diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js
index b02eb37206aeaf4afce436089860e72b03bcf5fd..1539ab806e4123a77abce4b411b34886b71335eb 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -6,3 +6,4 @@ export const EDITOR_LITE_INSTANCE_ERROR_NO_EL = __(
export const URI_PREFIX = 'gitlab';
export const CONTENT_UPDATE_DEBOUNCE = 250;
+export const SCHEMA_FILE_NAME_MATCH = '.gitlab-ci.yml';
diff --git a/app/assets/javascripts/editor/editor_ci_schema_ext.js b/app/assets/javascripts/editor/editor_ci_schema_ext.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a033ba70c3579ac2d5f7259dfb7eef6210e9bc4
--- /dev/null
+++ b/app/assets/javascripts/editor/editor_ci_schema_ext.js
@@ -0,0 +1,35 @@
+import { registerSchema } from '~/ide/utils';
+import Api from '~/api';
+
+// For CI config schemas the filename must match
+// '*.gitlab-ci.yml' regardless of project configuration.
+// https://gitlab.com/gitlab-org/gitlab/-/issues/293641
+import { SCHEMA_FILE_NAME_MATCH } from './constants';
+
+const getCiSchemaUri = ({ projectNamespace, projectPath, ref }) =>
+ Api.buildUrl(Api.projectFileSchemaPath)
+ .replace(':namespace_path', projectNamespace)
+ .replace(':project_path', projectPath)
+ .replace(':ref', ref)
+ .replace(':filename', SCHEMA_FILE_NAME_MATCH);
+
+export default {
+ /**
+ * Registers a schema in a model based on project properties
+ * and the name of the file that is currently edited.
+ *
+ * @param {Object} opts
+ * @param {String} opts.projectNamespace
+ * @param {String} opts.projectPath
+ * @param {String?} opts.ref
+ */
+ registerCiSchema({ projectNamespace, projectPath, ref = 'master' } = {}) {
+ const fileName = this.getModel()
+ .uri.path.split('/')
+ .pop();
+ registerSchema({
+ uri: getCiSchemaUri({ projectNamespace, projectPath, ref }),
+ fileMatch: [fileName],
+ });
+ },
+};
diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
index 22f2a32c9ac03df7f0c92866565a9a6396b73fc9..50463e43bb6778a7280ed693697e92bb0ed1de33 100644
--- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue
+++ b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
@@ -1,14 +1,45 @@
-
+
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 8268a907a29243c058f801aaecdc36f1c959409a..01ea865e829bd644ebff710341223978d598e1a5 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -14,7 +14,15 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return null;
}
- const { ciConfigPath, commitId, defaultBranch, newMergeRequestPath, projectPath } = el?.dataset;
+ const {
+ ciConfigPath,
+ commitId,
+ defaultBranch,
+ projectFullPath,
+ projectPath,
+ projectNamespace,
+ newMergeRequestPath,
+ } = el?.dataset;
Vue.use(VueApollo);
@@ -25,6 +33,11 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
return new Vue({
el,
apolloProvider,
+ provide: {
+ projectFullPath,
+ projectPath,
+ projectNamespace,
+ },
render(h) {
return h(PipelineEditorApp, {
props: {
@@ -32,7 +45,6 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
commitId,
defaultBranch,
newMergeRequestPath,
- projectPath,
},
});
},
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index b1c52ffa9205f421f31757ad52c1763c88953b86..fe0e999cf71be4968e9f2efd9838d543e23a0beb 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -31,16 +31,12 @@ export default {
TextEditor,
},
props: {
- projectPath: {
- type: String,
- required: true,
- },
- defaultBranch: {
+ commitId: {
type: String,
required: false,
default: null,
},
- commitId: {
+ defaultBranch: {
type: String,
required: false,
default: null,
@@ -54,6 +50,7 @@ export default {
required: true,
},
},
+ inject: ['projectFullPath'],
data() {
return {
ciConfigData: {},
@@ -72,7 +69,7 @@ export default {
query: getBlobContent,
variables() {
return {
- projectPath: this.projectPath,
+ projectPath: this.projectFullPath,
path: this.ciConfigPath,
ref: this.defaultBranch,
};
@@ -204,7 +201,7 @@ export default {
} = await this.$apollo.mutate({
mutation: commitCiFileMutation,
variables: {
- projectPath: this.projectPath,
+ projectPath: this.projectFullPath,
branch,
startBranch: this.defaultBranch,
message,
@@ -258,10 +255,20 @@ export default {
-
+
-
+
diff --git a/app/assets/javascripts/vue_shared/components/editor_lite.vue b/app/assets/javascripts/vue_shared/components/editor_lite.vue
index cfe3ce0a11cab1aa511bccdab3fffabf3baa525c..7218b84cf8a87f3dca06cc6ee5d290e9cf0fcc06 100644
--- a/app/assets/javascripts/vue_shared/components/editor_lite.vue
+++ b/app/assets/javascripts/vue_shared/components/editor_lite.vue
@@ -84,6 +84,9 @@ export default {
onFileChange() {
this.$emit('input', this.editor.getValue());
},
+ getEditor() {
+ return this.editor;
+ },
},
};
diff --git a/app/views/projects/ci/pipeline_editor/show.html.haml b/app/views/projects/ci/pipeline_editor/show.html.haml
index f1f8658fa3b59348e526c3021c3f610271d068b3..70695c59327034481ebb0573066984dd04bc170f 100644
--- a/app/views/projects/ci/pipeline_editor/show.html.haml
+++ b/app/views/projects/ci/pipeline_editor/show.html.haml
@@ -1,8 +1,10 @@
- page_title s_('Pipelines|Pipeline Editor')
#js-pipeline-editor{ data: { "ci-config-path": @project.ci_config_path_or_default,
- "project-path" => @project.full_path,
- "default-branch" => @project.default_branch,
"commit-id" => @project.commit ? @project.commit.id : '',
+ "default-branch" => @project.default_branch,
+ "project-full-path" => @project.full_path,
+ "project-path" => @project.path,
+ "project-namespace" => @project.namespace.path,
"new-merge-request-path" => namespace_project_new_merge_request_path,
} }
diff --git a/spec/frontend/editor/editor_ci_schema_ext_spec.js b/spec/frontend/editor/editor_ci_schema_ext_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..526d18675cc11b835d909299ccc144aca945f791
--- /dev/null
+++ b/spec/frontend/editor/editor_ci_schema_ext_spec.js
@@ -0,0 +1,76 @@
+import { languages } from 'monaco-editor';
+import EditorLite from '~/editor/editor_lite';
+import EditorCiSchemaExtension from '~/editor/editor_ci_schema_ext';
+
+describe('~/editor/editor_ci_config_ext', () => {
+ const mockBlobPath = '.gitlab-ci.yml';
+ let editor;
+ let instance;
+ let editorEl;
+
+ beforeEach(() => {
+ setFixtures('');
+ editorEl = document.getElementById('editor');
+ editor = new EditorLite();
+ instance = editor.createInstance({
+ el: editorEl,
+ blobPath: mockBlobPath,
+ blobContent: '',
+ });
+ instance.use(EditorCiSchemaExtension);
+ });
+
+ afterEach(() => {
+ instance.dispose();
+ editorEl.remove();
+ });
+
+ describe('registerCiSchema', () => {
+ beforeEach(() => {
+ jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions');
+ });
+
+ describe('register validations options with monaco for yaml language', () => {
+ it('with expected basic validation configuration', () => {
+ instance.registerCiSchema({ projectNamespace: 'namespace1', projectPath: 'project1' });
+
+ const expectedOptions = {
+ validate: true,
+ enableSchemaRequest: true,
+ hover: true,
+ completion: true,
+ };
+
+ expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith(
+ expect.objectContaining(expectedOptions),
+ );
+ });
+
+ it.each`
+ opts | expectedUri
+ ${{}} | ${'/namespace1/project1/-/schema/master/.gitlab-ci.yml'}
+ ${{ ref: 'REF' }} | ${'/namespace1/project1/-/schema/REF/.gitlab-ci.yml'}
+ ${{ projectNamespace: 'namespace2', projectPath: 'other-project' }} | ${'/namespace2/other-project/-/schema/master/.gitlab-ci.yml'}
+ `('with the expected schema for options "$opts"', ({ opts, expectedUri }) => {
+ instance.registerCiSchema({
+ projectNamespace: 'namespace1',
+ projectPath: 'project1',
+ ...opts,
+ });
+
+ const expectedOptions = expect.objectContaining({
+ schemas: [
+ {
+ uri: expectedUri,
+ fileMatch: [mockBlobPath],
+ },
+ ],
+ });
+
+ expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith(
+ expectedOptions,
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/text_editor_spec.js b/spec/frontend/pipeline_editor/components/text_editor_spec.js
index 18f71ebc95cc08f86476d3488f2a48e36ab7b44c..211bafa11e5de47a621331fea358f502d7b4e58c 100644
--- a/spec/frontend/pipeline_editor/components/text_editor_spec.js
+++ b/spec/frontend/pipeline_editor/components/text_editor_spec.js
@@ -1,15 +1,34 @@
-import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { shallowMount, mount } from '@vue/test-utils';
import EditorLite from '~/vue_shared/components/editor_lite.vue';
-import { mockCiYml } from '../mock_data';
-
+import EditorCiSchemaExtension from '~/editor/editor_ci_schema_ext';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
+import {
+ mockCiYml,
+ mockCiConfigPath,
+ mockProjectPath,
+ mockProjectNamespace,
+ mockCommitId,
+} from '../mock_data';
+
+jest.mock('~/editor/editor_ci_schema_ext');
+
describe('~/pipeline_editor/components/text_editor.vue', () => {
let wrapper;
const editorReadyListener = jest.fn();
- const createComponent = (attrs = {}, mountFn = shallowMount) => {
+ const createComponent = ({ props = {}, attrs = {} } = {}, mountFn = shallowMount) => {
wrapper = mountFn(TextEditor, {
+ provide: {
+ projectPath: mockProjectPath,
+ projectNamespace: mockProjectNamespace,
+ },
+ propsData: {
+ ciConfigPath: mockCiConfigPath,
+ commitId: mockCommitId,
+ ...props,
+ },
attrs: {
value: mockCiYml,
...attrs,
@@ -20,25 +39,46 @@ describe('~/pipeline_editor/components/text_editor.vue', () => {
});
};
- const findEditor = () => wrapper.find(EditorLite);
-
- it('contains an editor', () => {
+ beforeEach(() => {
createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findEditorLite = () => wrapper.find(EditorLite);
- expect(findEditor().exists()).toBe(true);
+ it('contains an editor', () => {
+ expect(findEditorLite().exists()).toBe(true);
});
it('editor contains the value provided', () => {
- expect(findEditor().props('value')).toBe(mockCiYml);
+ expect(findEditorLite().props('value')).toBe(mockCiYml);
});
it('editor is configured for .yml', () => {
- expect(findEditor().props('fileName')).toBe('*.yml');
+ expect(findEditorLite().props('fileName')).toBe(mockCiConfigPath);
});
- it('bubbles up events', () => {
- findEditor().vm.$emit('editor-ready');
+ it('bubbles up editor-ready event', () => {
+ createComponent({}, mount);
+
+ findEditorLite().vm.$emit('editor-ready');
expect(editorReadyListener).toHaveBeenCalled();
});
+
+ it('registers ci schema extension', async () => {
+ createComponent({}, mount);
+
+ await nextTick();
+
+ expect(EditorCiSchemaExtension.registerCiSchema).toHaveBeenCalledWith({
+ projectPath: mockProjectPath,
+ projectNamespace: mockProjectNamespace,
+ ref: mockCommitId,
+ });
+ });
});
diff --git a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
index b531f8af79747a3c3ef06808ec1d974ae22fc9d1..3e00852741550eff079fe304225874ded9fab20f 100644
--- a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
+++ b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js
@@ -5,7 +5,7 @@ import {
mockCiYml,
mockDefaultBranch,
mockLintResponse,
- mockProjectPath,
+ mockProjectFullPath,
} from '../mock_data';
import httpStatus from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
@@ -32,12 +32,12 @@ describe('~/pipeline_editor/graphql/resolvers', () => {
it('resolves lint data with type names', async () => {
const result = resolvers.Query.blobContent(null, {
- projectPath: mockProjectPath,
+ projectPath: mockProjectFullPath,
path: mockCiConfigPath,
ref: mockDefaultBranch,
});
- expect(Api.getRawFile).toHaveBeenCalledWith(mockProjectPath, mockCiConfigPath, {
+ expect(Api.getRawFile).toHaveBeenCalledWith(mockProjectFullPath, mockCiConfigPath, {
ref: mockDefaultBranch,
});
diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js
index d882490c272418ddd5f520c4ada39adc263e3a80..d783e561a77185cf9a8ced301608b982425e9ebc 100644
--- a/spec/frontend/pipeline_editor/mock_data.js
+++ b/spec/frontend/pipeline_editor/mock_data.js
@@ -1,4 +1,7 @@
-export const mockProjectPath = 'user1/project1';
+export const mockProjectFullPath = 'user1/project1';
+export const mockProjectPath = 'project1';
+export const mockProjectNamespace = 'user1';
+
export const mockDefaultBranch = 'master';
export const mockNewMergeRequestPath = '/-/merge_requests/new';
export const mockCommitId = 'aabbccdd';
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index ca54c97d2bb87ef48773a77c8977759a070c853f..5f26dc97ebb2e6b8e96212afcb0df9b0f02ad7a4 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -1,14 +1,6 @@
import { nextTick } from 'vue';
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
-import {
- GlAlert,
- GlButton,
- GlFormInput,
- GlFormTextarea,
- GlLoadingIcon,
- GlTabs,
- GlTab,
-} from '@gitlab/ui';
+import { GlAlert, GlFormInput, GlFormTextarea, GlLoadingIcon, GlTab } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
@@ -21,7 +13,9 @@ import {
mockCommitId,
mockCommitMessage,
mockDefaultBranch,
+ mockProjectFullPath,
mockProjectPath,
+ mockProjectNamespace,
mockNewMergeRequestPath,
} from './mock_data';
@@ -34,6 +28,18 @@ import TextEditor from '~/pipeline_editor/components/text_editor.vue';
const localVue = createLocalVue();
localVue.use(VueApollo);
+const MockEditorLite = {
+ template: '',
+ methods: {
+ getEditor() {
+ return {
+ use: jest.fn(),
+ registerCiSchema: jest.fn(),
+ };
+ },
+ },
+};
+
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
refreshCurrentPage: jest.fn(),
@@ -65,21 +71,21 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
wrapper = mountFn(PipelineEditorApp, {
+ provide: {
+ projectFullPath: mockProjectFullPath,
+ projectPath: mockProjectPath,
+ projectNamespace: mockProjectNamespace,
+ },
propsData: {
ciConfigPath: mockCiConfigPath,
commitId: mockCommitId,
defaultBranch: mockDefaultBranch,
- projectPath: mockProjectPath,
newMergeRequestPath: mockNewMergeRequestPath,
...props,
},
stubs: {
- GlTabs,
- GlButton,
CommitForm,
- EditorLite: {
- template: '',
- },
+ EditorLite: MockEditorLite,
TextEditor,
},
mocks: {
@@ -125,7 +131,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAlert = () => wrapper.find(GlAlert);
const findTabAt = i => wrapper.findAll(GlTab).at(i);
- const findTextEditor = () => wrapper.find(TextEditor);
+ const findTextEditor = () => wrapper.find(MockEditorLite);
const findCommitForm = () => wrapper.find(CommitForm);
const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon);
@@ -152,47 +158,68 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
expect(findTextEditor().exists()).toBe(false);
});
- describe('tabs', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('lazy tabs', () => {
+ it('displays editor tab, until editor is ready', async () => {
+ createComponent({ mountFn: mount });
+
+ expect(
+ findTabAt(0)
+ .find(TextEditor)
+ .exists(),
+ ).toBe(false);
+
+ await nextTick();
- it('displays tabs and their content', async () => {
expect(
findTabAt(0)
.find(TextEditor)
.exists(),
).toBe(true);
+ });
+
+ it('displays pipeline tab lazily, when tab is selected', async () => {
+ createComponent({ mountFn: mount });
+
expect(
findTabAt(1)
.find(PipelineGraph)
.exists(),
- ).toBe(true);
- });
+ ).toBe(false);
- it('displays editor tab lazily, until editor is ready', async () => {
- expect(findTabAt(0).attributes('lazy')).toBe('true');
+ await nextTick();
- findTextEditor().vm.$emit('editor-ready');
+ // select graph tab
+ wrapper.find('[data-testid="graph-tab-btn"]').trigger('click');
await nextTick();
- expect(findTabAt(0).attributes('lazy')).toBe(undefined);
+ expect(
+ findTabAt(1)
+ .find(PipelineGraph)
+ .exists(),
+ ).toBe(true);
});
});
describe('when data is set', () => {
- beforeEach(async () => {
- createComponent({ mountFn: mount });
-
- await wrapper.setData({
- content: mockCiYml,
- contentModel: mockCiYml,
+ beforeEach(() => {
+ createComponent({
+ options: {
+ data() {
+ return {
+ content: mockCiYml,
+ contentModel: mockCiYml,
+ };
+ },
+ },
+ mountFn: mount,
});
});
it('displays content after the query loads', () => {
expect(findLoadingIcon().exists()).toBe(false);
+
+ expect(findTextEditor().attributes('file-name')).toBe(mockCiConfigPath);
expect(findTextEditor().attributes('value')).toBe(mockCiYml);
});
@@ -202,7 +229,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
filePath: mockCiConfigPath,
lastCommitId: mockCommitId,
message: mockCommitMessage,
- projectPath: mockProjectPath,
+ projectPath: mockProjectFullPath,
startBranch: mockDefaultBranch,
};
diff --git a/spec/frontend/vue_shared/components/editor_lite_spec.js b/spec/frontend/vue_shared/components/editor_lite_spec.js
index 52502fcf64fc556fedb09b2a6682c79187cba759..cc11f76d01f8c8ef42513a97d062c1a7b51f193a 100644
--- a/spec/frontend/vue_shared/components/editor_lite_spec.js
+++ b/spec/frontend/vue_shared/components/editor_lite_spec.js
@@ -7,20 +7,22 @@ jest.mock('~/editor/editor_lite');
describe('Editor Lite component', () => {
let wrapper;
- const onDidChangeModelContent = jest.fn();
- const updateModelLanguage = jest.fn();
- const getValue = jest.fn();
- const setValue = jest.fn();
+ let mockInstance;
+
const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt';
const fileGlobalId = 'snippet_777';
- const createInstanceMock = jest.fn().mockImplementation(() => ({
- onDidChangeModelContent,
- updateModelLanguage,
- getValue,
- setValue,
- dispose: jest.fn(),
- }));
+ const createInstanceMock = jest.fn().mockImplementation(() => {
+ mockInstance = {
+ onDidChangeModelContent: jest.fn(),
+ updateModelLanguage: jest.fn(),
+ getValue: jest.fn(),
+ setValue: jest.fn(),
+ dispose: jest.fn(),
+ };
+ return mockInstance;
+ });
+
Editor.mockImplementation(() => {
return {
createInstance: createInstanceMock,
@@ -46,8 +48,8 @@ describe('Editor Lite component', () => {
});
const triggerChangeContent = val => {
- getValue.mockReturnValue(val);
- const [cb] = onDidChangeModelContent.mock.calls[0];
+ mockInstance.getValue.mockReturnValue(val);
+ const [cb] = mockInstance.onDidChangeModelContent.mock.calls[0];
cb();
@@ -92,12 +94,12 @@ describe('Editor Lite component', () => {
});
return nextTick().then(() => {
- expect(updateModelLanguage).toHaveBeenCalledWith(newFileName);
+ expect(mockInstance.updateModelLanguage).toHaveBeenCalledWith(newFileName);
});
});
it('registers callback with editor onChangeContent', () => {
- expect(onDidChangeModelContent).toHaveBeenCalledWith(expect.any(Function));
+ expect(mockInstance.onDidChangeModelContent).toHaveBeenCalledWith(expect.any(Function));
});
it('emits input event when the blob content is changed', () => {
@@ -117,6 +119,10 @@ describe('Editor Lite component', () => {
expect(wrapper.emitted()['editor-ready']).toBeDefined();
});
+ it('component API `getEditor()` returns the editor instance', () => {
+ expect(wrapper.vm.getEditor()).toBe(mockInstance);
+ });
+
describe('reaction to the value update', () => {
it('reacts to the changes in the passed value', async () => {
const newValue = 'New Value';
@@ -126,7 +132,7 @@ describe('Editor Lite component', () => {
});
await nextTick();
- expect(setValue).toHaveBeenCalledWith(newValue);
+ expect(mockInstance.setValue).toHaveBeenCalledWith(newValue);
});
it("does not update value if the passed one is exactly the same as the editor's content", async () => {
@@ -137,7 +143,7 @@ describe('Editor Lite component', () => {
});
await nextTick();
- expect(setValue).not.toHaveBeenCalled();
+ expect(mockInstance.setValue).not.toHaveBeenCalled();
});
});
});