yaml-language-server
Version:
1,105 lines (1,100 loc) • 82.4 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const yamlSettings_1 = require("../src/yamlSettings");
const serviceSetup_1 = require("./utils/serviceSetup");
const testHelper_1 = require("./utils/testHelper");
const chai_1 = require("chai");
const verifyError_1 = require("./utils/verifyError");
const path = __importStar(require("path"));
describe('Auto Completion Fix Tests', () => {
let languageSettingsSetup;
let languageService;
let languageHandler;
let yamlSettings;
let schemaProvider;
before(() => {
languageSettingsSetup = new serviceSetup_1.ServiceSetup().withCompletion().withSchemaFileMatch({
uri: 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.32.1-standalone-strict/all.json',
fileMatch: [testHelper_1.SCHEMA_ID],
});
const { languageService: langService, languageHandler: langHandler, yamlSettings: settings, schemaProvider: testSchemaProvider, } = (0, testHelper_1.setupLanguageService)(languageSettingsSetup.languageSettings);
languageService = langService;
languageHandler = langHandler;
yamlSettings = settings;
schemaProvider = testSchemaProvider;
});
/**
* Generates a completion list for the given document and caret (cursor) position.
* @param content The content of the document.
* @param line starts with 0 index
* @param character starts with 1 index
* @returns A list of valid completions.
*/
function parseSetup(content, line, character) {
const testTextDocument = (0, testHelper_1.setupSchemaIDTextDocument)(content);
yamlSettings.documents = new yamlSettings_1.TextDocumentTestManager();
yamlSettings.documents.set(testTextDocument);
return languageHandler.completionHandler({
position: vscode_languageserver_types_1.Position.create(line, character),
textDocument: testTextDocument,
});
}
/**
* Generates a completion list for the given document and caret (cursor) position.
* @param content The content of the document.
* The caret is located in the content using `|` bookends.
* For example, `content = 'ab|c|d'` places the caret over the `'c'`, at `position = 2`
* @returns A list of valid completions.
*/
function parseCaret(content) {
const { position, content: content2 } = (0, testHelper_1.caretPosition)(content);
const testTextDocument = (0, testHelper_1.setupSchemaIDTextDocument)(content2);
yamlSettings.documents = new yamlSettings_1.TextDocumentTestManager();
yamlSettings.documents.set(testTextDocument);
return languageHandler.completionHandler({
position: testTextDocument.positionAt(position),
textDocument: testTextDocument,
});
}
afterEach(() => {
schemaProvider.deleteSchema(testHelper_1.SCHEMA_ID);
languageService.configure(languageSettingsSetup.languageSettings);
});
it('should show completion on map under array', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'array',
items: {
type: 'object',
properties: {
from: {
type: 'object',
properties: {
foo: {
type: 'boolean',
},
},
},
},
},
});
const content = '- from:\n | |'; // len: 12, pos: 11
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items).lengthOf(1);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('foo', 'foo: ', 1, 3, 1, 4, 10, 2, {
documentation: '',
}));
});
it('completion with array objects', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'array',
items: {
type: 'object',
properties: {
prop1: {
type: 'string',
},
prop2: {
type: 'string',
},
prop3: {
type: 'string',
},
},
},
});
const content = '- prop1: a\n | |'; // len: 12, pos: 11
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items).lengthOf(2);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('prop2', 'prop2: ', 1, 3, 1, 4, 10, 2, {
documentation: '',
}));
(0, chai_1.expect)(completion.items[1]).eql((0, verifyError_1.createExpectedCompletion)('prop3', 'prop3: ', 1, 3, 1, 4, 10, 2, {
documentation: '',
}));
});
it('should show completion on array empty array item', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'array',
items: {
type: 'object',
properties: {
from: {
type: 'object',
properties: {
foo: {
type: 'boolean',
},
},
},
},
},
});
const content = '- '; // len: 2
const completion = await parseSetup(content, 0, 2);
(0, chai_1.expect)(completion.items).lengthOf(1);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('from', 'from:\n ', 0, 2, 0, 2, 10, 2, {
documentation: '',
}));
});
it('should show completion items in the middle of map in array', async () => {
const content = `apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: test
image: alpine
`; // len: 90
const completion = await parseSetup(content, 7, 6);
(0, chai_1.expect)(completion.items).length.greaterThan(1);
});
it('should show completion on array item on first line', async () => {
const content = '-d'; // len: 2
const completion = await parseSetup(content, 0, 1);
(0, chai_1.expect)(completion.items).is.empty;
});
it('should complete without error on map inside array', async () => {
const content = '- foo\n- bar:\n so'; // len: 19
const completion = await parseSetup(content, 2, 6);
(0, chai_1.expect)(completion.items).is.empty;
});
it('should complete array', async () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const schema = require(path.join(__dirname, './fixtures/test-nested-object-array.json'));
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = `objA:
- name: nameA1
objB:
size: midle
name: nameB2
`; // len: 67
const completion = await parseSetup(content, 2, 4);
(0, chai_1.expect)(completion.items).is.not.empty;
});
it('should complete array item for "oneOf" schema', async () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const schema = require(path.join(__dirname, './fixtures/test-completion-oneOf.json'));
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = `metadata:
Selector:
query:
-
`; // len: 42
const completion = await parseSetup(content, 3, 8);
(0, chai_1.expect)(completion.items).length(5);
(0, chai_1.expect)(completion.items.map((it) => it.label)).to.have.members(['NOT', 'attribute', 'operation', 'value', 'FUNC_item']);
});
it('Autocomplete with short nextLine - nested object', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
example: {
type: 'object',
properties: {
sample: {
type: 'object',
properties: {
detail: {
type: 'object',
},
},
},
},
},
a: {
type: 'string',
description: 'short prop name because of distance to the cursor',
},
},
});
const content = 'example:\n sample:\n '; // len: 23
const completion = await parseSetup(content + '\na: test', 2, 4);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('detail', 'detail:\n ', 2, 4, 2, 4, 10, 2, {
documentation: '',
}));
});
it('Should suggest valid matches from oneOf', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
oneOf: [
{
type: 'object',
properties: {
spec: {
type: 'object',
},
},
},
{
properties: {
spec: {
type: 'object',
required: ['bar'],
properties: {
bar: {
type: 'string',
},
},
},
},
},
],
});
const content = '|s|'; // len: 1, pos: 1
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('spec', 'spec:\n bar: ', 0, 0, 0, 1, 10, 2, {
documentation: '',
}));
});
it('Should suggest all the matches from allOf', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
allOf: [
{
type: 'object',
properties: {
spec: {
type: 'object',
},
},
},
{
properties: {
spec: {
type: 'object',
required: ['bar'],
properties: {
bar: {
type: 'string',
},
},
},
},
},
],
});
const content = '|s|'; // len: 1, pos: 1
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.length).equal(2);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('spec', 'spec:\n ', 0, 0, 0, 1, 10, 2, {
documentation: '',
}));
(0, chai_1.expect)(completion.items[1]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('spec', 'spec:\n bar: ', 0, 0, 0, 1, 10, 2, {
documentation: '',
}));
});
it('Autocomplete with a new line inside the object', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
example: {
type: 'object',
properties: {
sample: {
type: 'object',
properties: {
prop1: {
type: 'string',
},
prop2: {
type: 'string',
},
},
},
},
},
},
});
const content = 'example:\n sample:\n |\n| prop2: value2'; // len: 41, pos: 23
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('prop1', 'prop1: ', 2, 4, 2, 4, 10, 2, {
documentation: '',
}));
});
it('Autocomplete on the first array item', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
examples: {
type: 'array',
items: {
type: 'object',
properties: {
sample: {
type: 'object',
properties: {
prop1: {
type: 'string',
},
},
},
},
},
},
},
});
const content = 'examples:\n |\n| - sample:\n prop1: value1'; // len: 44, pos: 12
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('- (array item) object', '- ', 1, 2, 1, 2, 9, 2, {
documentation: {
kind: 'markdown',
value: 'Create an item of an array type `object`\n ```\n- \n```',
},
}));
});
it('Array of enum autocomplete of irregular order', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
apiVersion: {
type: 'string',
},
metadata: {
type: 'object',
properties: {
name: {
type: 'string',
},
},
},
kind: {
type: 'string',
enum: ['Pod', 'PodTemplate'],
},
},
});
const content = 'kind: Po'; // len: 8
const completion = await parseSetup(content, 1, 9);
(0, chai_1.expect)(completion.items.length).equal(2);
(0, chai_1.expect)(completion.items[0].insertText).equal('Pod');
(0, chai_1.expect)(completion.items[1].insertText).equal('PodTemplate');
});
it('Test that properties have enum of string type with number', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
version: {
type: 'array',
items: {
enum: ['12.1', 13, '13.1', '14.0', 'all', 14.4, false, null, ['test']],
type: ['string', 'integer', 'number', 'boolean', 'object', 'array'],
},
},
},
});
const content = 'version:\n - ';
const completion = await parseSetup(content, 2, 0);
(0, chai_1.expect)(completion.items).lengthOf(9);
(0, chai_1.expect)(completion.items[0].insertText).equal('"12.1"');
(0, chai_1.expect)(completion.items[1].insertText).equal('13');
(0, chai_1.expect)(completion.items[4].insertText).equal('all');
(0, chai_1.expect)(completion.items[5].insertText).equal('14.4');
(0, chai_1.expect)(completion.items[6].insertText).equal('false');
(0, chai_1.expect)(completion.items[7].insertText).equal('null');
(0, chai_1.expect)(completion.items[8].insertText).equal('\n - ${1:test}\n');
});
it('Autocomplete indent on array when parent is array', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
examples: {
type: 'array',
items: {
type: 'object',
properties: {
objectWithArray: {
type: 'array',
items: {
type: 'string',
},
},
},
},
},
},
});
const content = 'examples:\n - '; // len: 14
const completion = await parseSetup(content, 1, 4);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('objectWithArray', 'objectWithArray:\n - ${1}', 1, 4, 1, 4, 10, 2, {
documentation: '',
}));
});
it('Autocomplete indent on array object when parent is array', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
examples: {
type: 'array',
items: {
type: 'object',
properties: {
objectWithArray: {
type: 'array',
items: {
type: 'object',
required: ['item', 'item2'],
properties: {
item: { type: 'string' },
item2: { type: 'string' },
},
},
},
},
},
},
},
});
const content = 'examples:\n - '; // len: 14
const completion = await parseSetup(content, 1, 4);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('objectWithArray', 'objectWithArray:\n - item: $1\n item2: $2', 1, 4, 1, 4, 10, 2, {
documentation: '',
}));
});
it('Autocomplete indent on array object when parent is array of an array', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
array1: {
type: 'array',
items: {
type: 'object',
required: ['thing1'],
properties: {
thing1: {
type: 'object',
required: ['array2'],
properties: {
array2: {
type: 'array',
items: {
type: 'object',
required: ['thing2', 'type'],
properties: {
type: {
type: 'string',
},
thing2: {
type: 'object',
required: ['item1', 'item2'],
properties: {
item1: { type: 'string' },
item2: { type: 'string' },
},
},
},
},
},
},
},
},
},
},
},
});
const content = 'array1:\n - ';
const completion = await parseSetup(content, 1, 4);
(0, chai_1.expect)(completion.items[0].insertText).to.be.equal('thing1:\n array2:\n - type: $1\n thing2:\n item1: $2\n item2: $3');
});
describe('array indent on different index position', () => {
const schema = {
type: 'object',
properties: {
objectWithArray: {
type: 'array',
items: {
type: 'object',
required: ['item', 'item2'],
properties: {
item: { type: 'string' },
item2: {
type: 'object',
required: ['prop1', 'prop2'],
properties: {
prop1: { type: 'string' },
prop2: { type: 'string' },
},
},
},
},
},
},
};
it('array indent on the first item', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'objectWithArray:\n - '; // len: 21
const completion = await parseSetup(content, 1, 4);
(0, chai_1.expect)(completion.items.length).equal(3);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('item', 'item: ', 1, 4, 1, 4, 10, 2, {
documentation: '',
}));
(0, chai_1.expect)(completion.items[2]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('item2', 'item2:\n prop1: $1\n prop2: $2', 1, 4, 1, 4, 10, 2, {
documentation: '',
}));
});
it('array indent on the second item', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'objectWithArray:\n - item: first line\n '; // len: 42
const completion = await parseSetup(content, 2, 4);
(0, chai_1.expect)(completion.items.length).equal(2);
(0, chai_1.expect)(completion.items[0]).to.be.deep.equal((0, verifyError_1.createExpectedCompletion)('item2', 'item2:\n prop1: $1\n prop2: $2', 2, 4, 2, 4, 10, 2, {
documentation: '',
}));
});
});
describe('merge properties from anyOf objects', () => {
it('should merge different simple values', async () => {
const schema = {
anyOf: [
{
properties: {
simplePropWithSimpleValue: { type: 'string', const: 'const value' },
},
},
{
properties: {
simplePropWithSimpleValue: { type: 'boolean', default: false },
},
},
{
properties: {
simplePropWithSimpleValue: { type: 'null', default: null },
},
},
{
properties: {
simplePropWithSimpleValue: { type: 'string' },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = '';
const completion = await parseSetup(content, 0, 1);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0].insertText).to.be.equal('simplePropWithSimpleValue: ${1|const value,false,null|}');
});
it('should autocomplete as single item with same value', async () => {
const schema = {
anyOf: [
{
properties: {
simplePropWithSameValue: { type: 'string', const: 'const value 1' },
obj1: { properties: { prop1: { type: 'string' } } },
},
},
{
properties: {
simplePropWithSameValue: { type: 'string', const: 'const value 1' },
obj1: { properties: { prop1: { type: 'string' } } },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = '';
const completion = await parseSetup(content, 0, 1);
(0, chai_1.expect)(completion.items.length).equal(2);
(0, chai_1.expect)(completion.items[0].insertText).to.be.equal('simplePropWithSameValue: const value 1');
(0, chai_1.expect)(completion.items[1].insertText).to.be.equal('obj1:\n ');
});
it('should not merge objects', async () => {
const schema = {
anyOf: [
{
properties: {
obj1: { properties: { prop1: { type: 'string' } }, required: ['prop1'] },
},
},
{
properties: {
obj1: { properties: { prop2: { type: 'string', const: 'value' } }, required: ['prop2'] },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = '';
const completion = await parseSetup(content, 0, 1);
(0, chai_1.expect)(completion.items.length).equal(2);
(0, chai_1.expect)(completion.items[0].label).to.be.equal('obj1');
(0, chai_1.expect)(completion.items[0].insertText).to.be.equal('obj1:\n prop1: ');
(0, chai_1.expect)(completion.items[1].label).to.be.equal('obj1');
(0, chai_1.expect)(completion.items[1].insertText).to.be.equal('obj1:\n prop2: ${1:value}');
});
it('Autocomplete should not suggest items for parent object', async () => {
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, {
type: 'object',
properties: {
scripts: {
type: 'object',
properties: {
sample: {
type: 'string',
},
},
},
scripts2: {
type: 'string',
},
},
});
const content = 'scripts: \n sample: | |';
const completion = await parseSetup(content, 0, 9); // before line brake
(0, chai_1.expect)(completion.items.length).equal(0);
});
it('autoCompletion when value is null inside anyOf object', async () => {
const schema = {
anyOf: [
{
properties: {
prop: {
const: 'const value',
},
},
},
{
properties: {
prop: {
type: 'null',
},
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = '';
const completion = await parseSetup(content, 0, 6);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0].label).to.be.equal('prop');
(0, chai_1.expect)(completion.items[0].insertText).to.be.equal('prop: ${1|const value,null|}');
});
it('should take all sub-schemas when value has not been set (cursor in the middle of the empty space)', async () => {
const schema = {
anyOf: [
{
properties: {
prop: { type: 'null' },
},
},
{
properties: {
prop: { const: 'const value' },
},
},
{
properties: {
prop: { const: 5 },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: | | ';
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['const value', '5', 'null']);
});
it('should take only null sub-schema when value is "null"', async () => {
const schema = {
anyOf: [
{
properties: {
prop: { type: 'null' },
},
},
{
properties: {
prop: { const: 'const value' },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: null';
const completion = await parseSetup(content, 0, content.length);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['null']);
});
it('should take only one sub-schema because first sub-schema does not match', async () => {
const schema = {
anyOf: [
{
properties: {
prop: { const: 'const value' },
},
},
{
properties: {
prop: { const: 'const value2' },
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: const value2';
const completion = await parseSetup(content, 0, content.length);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['const value2']);
});
it('should match only second sub-schema because the first one does not match', async () => {
const schema = {
anyOf: [
{
properties: {
prop: {
const: 'typeA',
},
propA: {},
},
},
{
properties: {
prop: {
const: 'typeB',
},
propB: {},
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: typeB\n|\n|';
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['propB']);
});
it('should suggest from all sub-schemas even if nodes properties match better other schema', async () => {
// this is a case when we have a better match in the second schema but we should still suggest from the first one
// it works because `prop: ` will evaluate to `enumValueMatch = true` for both schemas
const schema = {
anyOf: [
{
properties: {
prop: {
const: 'typeA',
},
},
},
{
properties: {
prop: {
const: 'typeB',
},
propB: {},
},
},
],
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: |\n|\npropB: B';
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['typeA', 'typeB'], 'with null value');
const content2 = 'prop: typ|\n|\npropB: B';
const completion2 = await parseCaret(content2);
(0, chai_1.expect)(completion2.items.map((i) => i.label)).to.be.deep.eq(['typeA', 'typeB'], 'with prefix value');
});
it('should suggest both sub-schemas for anyof array', async () => {
const schema = {
properties: {
entities: {
type: 'array',
items: {
anyOf: [
{
enum: ['enum1'],
},
{
type: 'object',
title: 'entity object',
properties: {
entityProp: { type: 'string' },
},
required: ['entityProp'],
},
],
},
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'entities:\n - |\n|';
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.map((i) => i.label)).to.be.deep.eq(['enum1', 'entityProp', 'entity object']);
});
});
describe('conditional schemas', () => {
it('should not suggest value from conditional if branch', async () => {
const schema = {
required: ['on'],
type: 'object',
properties: {
on: {
type: 'object',
properties: {
workflow_dispatch: {
$comment: 'https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/',
description: "You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run.",
properties: {
inputs: {
$comment: 'https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputs',
description: 'Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids.',
type: 'object',
patternProperties: {
'^[_a-zA-Z][a-zA-Z0-9_-]*$': {
$ref: '#/definitions/workflowDispatchInput',
},
},
additionalProperties: false,
},
},
additionalProperties: false,
},
},
},
jobs: {
type: 'object',
},
},
definitions: {
workflowDispatchInput: {
$comment: 'https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_id',
description: "A string identifier to associate with the input. The value of <input_id> is a map of the input's metadata. The <input_id> must be a unique identifier within the inputs object. The <input_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.",
type: 'object',
properties: {
description: {
$comment: 'https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddescription',
description: 'A string description of the input parameter.',
type: 'string',
},
deprecationMessage: {
description: 'A string shown to users using the deprecated input.',
type: 'string',
},
required: {
$comment: 'https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_idrequired',
description: 'A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required.',
type: 'boolean',
},
default: {
$comment: 'https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddefault',
description: "A string representing the default value. The default value is used when an input parameter isn't specified in a workflow file.",
},
type: {
$comment: 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputsinput_idtype',
description: 'A string representing the type of the input.',
type: 'string',
enum: ['string', 'choice', 'boolean', 'number', 'environment'],
},
options: {
$comment: 'https://github.blog/changelog/2021-11-10-github-actions-input-types-for-manual-workflows',
description: 'The options of the dropdown list, if the type is a choice.',
type: 'array',
items: {
type: 'string',
},
minItems: 1,
},
},
allOf: [
{
if: {
properties: {
type: {
const: 'string',
},
},
required: ['type'],
},
then: {
properties: {
default: {
type: 'string',
},
},
},
},
{
if: {
properties: {
type: {
const: 'boolean',
},
},
required: ['type'],
},
then: {
properties: {
default: {
type: 'boolean',
},
},
},
},
{
if: {
properties: {
type: {
const: 'number',
},
},
required: ['type'],
},
then: {
properties: {
default: {
type: 'number',
},
},
},
},
{
if: {
properties: {
type: {
const: 'environment',
},
},
required: ['type'],
},
then: {
properties: {
default: {
type: 'string',
},
},
},
},
{
if: {
properties: {
type: {
const: 'choice',
},
},
required: ['type'],
},
then: {
required: ['options'],
},
},
],
additionalProperties: false,
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = `name: Check configuration schema changes
on:
workflow_dispatch:
inputs:
sdaf:
jobs:
foo:
uses: repo/test.yml@main`;
const completion = await parseSetup(content, 7, 8);
const labels = completion.items.map((i) => i.label);
(0, chai_1.expect)(labels).to.include.members(['description', 'required', 'default', 'type', 'options']);
(0, chai_1.expect)(labels.filter((label) => label === 'GitHub Workflow')).to.be.empty;
});
});
describe('extra space after cursor', () => {
it('simple const', async () => {
const schema = {
properties: {
prop: {
const: 'const',
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'prop: | | '; // len: 8, pos: 6
const completion = await parseCaret(content);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0].label).to.be.equal('const');
(0, chai_1.expect)(completion.items[0].textEdit).to.be.deep.equal({ newText: 'const', range: vscode_languageserver_types_1.Range.create(0, 6, 0, 8) });
});
it('partial key with trailing spaces', async () => {
const schema = {
properties: {
name: {
const: 'my name',
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'na ';
const completion = await parseSetup(content, 0, 2);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('name', 'name: my name', 0, 0, 0, 4, 10, 2, {
documentation: '',
}));
});
it('partial key with trailing spaces with new line', async () => {
const schema = {
properties: {
name: {
const: 'my name',
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = 'na \n';
const completion = await parseSetup(content, 0, 2);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('name', 'name: my name', 0, 0, 0, 5, 10, 2, {
documentation: '',
}));
});
it('partial key with leading and trailing spaces', async () => {
const schema = {
properties: {
name: {
const: 'my name',
},
},
};
schemaProvider.addSchema(testHelper_1.SCHEMA_ID, schema);
const content = ' na ';
const completion = await parseSetup(content, 0, 2);
(0, chai_1.expect)(completion.items.length).equal(1);
(0, chai_1.expect)(completion.items[0]).eql((0, verifyError_1.createExpectedCompletion)('name', 'name: my name', 0, 2, 0, 4, 10, 2, {
documentation: '