- 保持 main 参数运行协议并在失焦或销毁前刷新输出分析 - 按输入名称生成 JavaScript 和 Python 补全且隐藏旧版 _result - 补充复杂签名与命名参数补全测试
177 lines
6.0 KiB
TypeScript
177 lines
6.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
createBusinessCompletions,
|
|
normalizeCodeEngine,
|
|
resolveBusinessCompletionContext,
|
|
shouldAutoAppendCallParens,
|
|
shouldSkipBusinessCompletion,
|
|
} from './codeCompletion';
|
|
|
|
describe('codeCompletion utils', () => {
|
|
it('should normalize engine aliases', () => {
|
|
expect(normalizeCodeEngine('py')).toBe('python');
|
|
expect(normalizeCodeEngine('python')).toBe('python');
|
|
expect(normalizeCodeEngine('js')).toBe('javascript');
|
|
expect(normalizeCodeEngine('javascript')).toBe('javascript');
|
|
expect(normalizeCodeEngine('')).toBe('javascript');
|
|
});
|
|
|
|
it('should create business completion list with stable priority and dedupe', () => {
|
|
const completions = createBusinessCompletions('python', [
|
|
{ name: 'input.text', resolved: true },
|
|
{ name: 'input.text', resolved: false },
|
|
{ name: 'question', resolved: false },
|
|
]);
|
|
|
|
expect(completions[0]).toMatchObject({
|
|
label: 'main',
|
|
type: 'snippet',
|
|
boost: 1000,
|
|
});
|
|
expect(String(completions[0].apply)).toContain('def main(question):');
|
|
expect(completions.some((item) => item.label === '_result')).toBe(false);
|
|
|
|
expect(
|
|
completions.filter((item) => item.label === 'input.text').length,
|
|
).toBe(1);
|
|
expect(
|
|
completions.find((item) => item.label === 'input.text'),
|
|
).toMatchObject({
|
|
apply: '{{input.text}}',
|
|
detail: '参数模板',
|
|
});
|
|
expect(completions.find((item) => item.label === 'question')).toMatchObject(
|
|
{
|
|
apply: '{{question}}',
|
|
detail: '参数模板(未映射)',
|
|
},
|
|
);
|
|
|
|
expect(
|
|
completions.some(
|
|
(item) => item.type === 'snippet' && item.label === 'if-else',
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it('should prefer main return snippets for javascript output', () => {
|
|
const completions = createBusinessCompletions('javascript', [
|
|
{ name: 'data', resolved: true },
|
|
{ name: 'question', resolved: true },
|
|
]);
|
|
const mainCompletion = completions.find((item) => item.label === 'main');
|
|
const resultCompletion = completions.find(
|
|
(item) => item.label === 'result-object',
|
|
);
|
|
|
|
expect(mainCompletion).toMatchObject({
|
|
type: 'snippet',
|
|
detail: '代码节点入口',
|
|
boost: 1000,
|
|
});
|
|
expect(String(mainCompletion?.apply)).toContain(
|
|
'function main(data, question)',
|
|
);
|
|
expect(String(mainCompletion?.apply)).toContain('return {');
|
|
expect(String(mainCompletion?.apply)).not.toContain('_result');
|
|
expect(String(resultCompletion?.apply)).toBe(
|
|
"return { message: 'ok', data };",
|
|
);
|
|
});
|
|
|
|
it('should prefer main return snippets for python output', () => {
|
|
const completions = createBusinessCompletions('python', [
|
|
{ name: 'data', resolved: true },
|
|
{ name: 'question', resolved: true },
|
|
]);
|
|
const mainCompletion = completions.find((item) => item.label === 'main');
|
|
const resultCompletion = completions.find(
|
|
(item) => item.label === 'result-object',
|
|
);
|
|
|
|
expect(mainCompletion).toMatchObject({
|
|
type: 'snippet',
|
|
detail: '代码节点入口',
|
|
boost: 1000,
|
|
});
|
|
expect(String(mainCompletion?.apply)).toContain(
|
|
'def main(data, question):',
|
|
);
|
|
expect(String(mainCompletion?.apply)).toContain('return {');
|
|
expect(String(mainCompletion?.apply)).not.toContain('_result');
|
|
expect(String(resultCompletion?.apply)).toContain(
|
|
'def main(data, question):',
|
|
);
|
|
expect(String(resultCompletion?.apply)).toContain(
|
|
"return {'message': 'ok', 'data': data}",
|
|
);
|
|
});
|
|
|
|
it('should not expose the legacy object-input contract in python snippets', () => {
|
|
const completions = createBusinessCompletions('python', []);
|
|
const snippets = completions.filter((item) => item.type === 'snippet');
|
|
|
|
expect(snippets).toHaveLength(4);
|
|
for (const snippet of snippets) {
|
|
expect(String(snippet.apply)).not.toContain('inputs');
|
|
expect(String(snippet.apply)).not.toContain('_result');
|
|
}
|
|
});
|
|
|
|
it('should detect blocked nodes for comment/string/property contexts', () => {
|
|
expect(shouldSkipBusinessCompletion('Comment')).toBe(true);
|
|
expect(shouldSkipBusinessCompletion('LineComment')).toBe(true);
|
|
expect(shouldSkipBusinessCompletion('String')).toBe(true);
|
|
expect(shouldSkipBusinessCompletion('TemplateString')).toBe(true);
|
|
expect(shouldSkipBusinessCompletion('PropertyName')).toBe(true);
|
|
expect(shouldSkipBusinessCompletion('VariableName')).toBe(false);
|
|
});
|
|
|
|
it('should resolve template context for parameter token completion', () => {
|
|
const source = 'value = {{input.use';
|
|
const pos = source.length;
|
|
const context = resolveBusinessCompletionContext(source, pos);
|
|
expect(context).toBeTruthy();
|
|
expect(context?.from).toBe(8);
|
|
expect(context?.validFor).toBeInstanceOf(RegExp);
|
|
expect(context?.validFor.source).toBe('^[\\w.]*$');
|
|
});
|
|
|
|
it('should resolve word context and skip property access context', () => {
|
|
const source = '_result.value';
|
|
expect(resolveBusinessCompletionContext(source, source.length)).toBeNull();
|
|
|
|
const wordSource = 'ret';
|
|
const context = resolveBusinessCompletionContext(
|
|
wordSource,
|
|
wordSource.length,
|
|
);
|
|
expect(context).toBeTruthy();
|
|
expect(context?.from).toBe(0);
|
|
expect(context?.validFor).toBeInstanceOf(RegExp);
|
|
expect(context?.validFor.source).toBe('^[\\w$]*$');
|
|
});
|
|
|
|
it('should auto append call parens for function/method completions only', () => {
|
|
expect(
|
|
shouldAutoAppendCallParens({ label: 'print', type: 'function' }, ''),
|
|
).toBe(true);
|
|
expect(
|
|
shouldAutoAppendCallParens({ label: 'run', type: 'method' }, ''),
|
|
).toBe(true);
|
|
expect(
|
|
shouldAutoAppendCallParens({ label: 'count', type: 'variable' }, ''),
|
|
).toBe(false);
|
|
expect(
|
|
shouldAutoAppendCallParens({ label: 'print', type: 'function' }, '('),
|
|
).toBe(false);
|
|
expect(
|
|
shouldAutoAppendCallParens(
|
|
{ label: 'call', type: 'function', apply: 'call()' },
|
|
'',
|
|
),
|
|
).toBe(false);
|
|
});
|
|
});
|