diff options
Diffstat (limited to 'tests/drivers/ext_cache_test.js')
| -rw-r--r-- | tests/drivers/ext_cache_test.js | 27 |
1 files changed, 26 insertions, 1 deletions
diff --git a/tests/drivers/ext_cache_test.js b/tests/drivers/ext_cache_test.js index 11bf50b..f389fcc 100644 --- a/tests/drivers/ext_cache_test.js +++ b/tests/drivers/ext_cache_test.js @@ -29,8 +29,15 @@ const stub = { appendMarkdown(v) { this.value += v; return this; } appendCodeblock(v, language) { this.value += '\n```' + language + '\n' + v + '\n```\n'; return this; } }, + CompletionItem: class { constructor(label, kind) { Object.assign(this, { label, kind }); } }, + CompletionList: class { constructor(items, isIncomplete) { Object.assign(this, { items, isIncomplete }); } }, + CompletionItemKind: { Variable: 5 }, + CompletionItemTag: { Deprecated: 1 }, + CodeActionKind: { QuickFix: 'quickfix' }, + CodeAction: class { constructor(title, kind) { Object.assign(this, { title, kind }); } }, + WorkspaceEdit: class { constructor() { this.edits = []; } replace(uri, range, newText) { this.edits.push({ uri, range, newText }); } }, Hover: class { constructor(c) { this.contents = c; } }, - Uri: { parse: s => ({ toString: () => s, fsPath: decodeURIComponent(s.replace(/^file:\/\//, '')) }) }, + Uri: { file: p => ({ toString: () => 'file://' + p, fsPath: p }), parse: s => ({ toString: () => s, fsPath: decodeURIComponent(s.replace(/^file:\/\//, '')) }) }, workspace: { workspaceFolders: undefined, textDocuments: world.textDocuments, @@ -41,6 +48,7 @@ const stub = { for (const k of ['onDidChange', 'onDidCreate', 'onDidDelete']) w[k] = f => { w.handlers[k] = f; return { dispose() {} }; }; return w; }, + registerTextDocumentContentProvider: () => ({ dispose() {} }), ...['onDidChangeConfiguration', 'onDidOpenTextDocument', 'onDidChangeTextDocument', 'onDidSaveTextDocument', 'onDidCloseTextDocument'] .reduce((o, k) => { o[k] = f => { world.listeners[k] = f; return { dispose() {} }; }; return o; }, {}), }, @@ -54,7 +62,12 @@ const stub = { delete() {}, dispose() {}, }), registerDocumentSemanticTokensProvider: () => ({ dispose() {} }), + registerCompletionItemProvider: (sel, p) => { world.completion = p; return { dispose() {} }; }, + registerCodeActionsProvider: (sel, p) => { world.actions = p; return { dispose() {} }; }, registerHoverProvider: (sel, p) => { world.hover = p; return { dispose() {} }; }, + registerDocumentFormattingEditProvider: () => ({ dispose() {} }), + registerDefinitionProvider: (sel, p) => { world.definition = p; return { dispose() {} }; }, + registerReferenceProvider: (sel, p) => { world.references = p; return { dispose() {} }; }, }, commands: { registerCommand: () => ({ dispose() {} }) }, }; @@ -116,6 +129,9 @@ const mainUri = 'file://' + main; const hv = world.hover && await world.hover.provideHover(mainDoc, { line: 1, character: 4 }); const hc = hv && hv.contents[0]; check('the hover is markdown with a zc code block, parsed and not escaped', hc && !hc.escaped && /^```zc\n/.test(hc.value) && /\n```/.test(hc.value), JSON.stringify(hc)); + const suggestions = await world.completion.provideCompletionItems(mainDoc, { line: 1, character: 4 }, { isCancellationRequested: false }); + check('completion provider returns signatures and replacement ranges', suggestions instanceof stub.CompletionList && suggestions.items.some(c => c.detail && c.range && c.insertText), JSON.stringify(suggestions && suggestions.items.slice(0, 2))); + check('cancelled completion requests are ignored', await world.completion.provideCompletionItems(mainDoc, { line: 1, character: 4 }, { isCancellationRequested: true }) === null); check('session one: the answer is saved under a key', savedKeys.length === 1 && /^[0-9a-f]{64}$/.test(savedKeys[0]), JSON.stringify(savedKeys)); ext.deactivate(); await sleep(700); @@ -146,6 +162,15 @@ const mainUri = 'file://' + main; await sleep(200); check('the old tree is answered from the cache, without asking the server', world.lines.some(l => /from the cache/.test(l)) && !world.lines.some(l => /-> textDocument\/didChange/.test(l)), JSON.stringify(world.lines.slice(-4))); + const expiredDoc = doc(path.join(root, 'expired.zc'), 'include rt; fn main() {\n let used = rt.new(int, 3);\n rt.free(used);\n \n}'); + const expiredList = await world.completion.provideCompletionItems(expiredDoc, { line: 3, character: 1 }, { isCancellationRequested: false }); + const expiredItem = expiredList && expiredList.items.find(c => c.label === 'used'); + check('completion maps consumed lifetimes to the editor strikethrough tag', expiredItem && expiredItem.tags?.[0] === stub.CompletionItemTag.Deprecated && /lifetime ended/.test(expiredItem.documentation?.value)); + const actionDoc = doc(path.join(root, 'imports.zc'), 'fn main() { rt.put_n(7); }'); + const actions = await world.actions.provideCodeActions(actionDoc, new stub.Range(0, 15, 0, 15), {}, { isCancellationRequested: false }); + check('quick fixes reach the editor as workspace edits', actions.some(a => a.kind === 'quickfix' && a.edit.edits.some(e => e.newText === 'include rt;\n'))); + const cancelled = await world.actions.provideCodeActions(actionDoc, new stub.Range(0, 15, 0, 15), {}, { isCancellationRequested: true }); + check('cancelled quick-fix requests produce no editor edits', cancelled.length === 0); ext.deactivate(); await sleep(700); fs.rmSync(root, { recursive: true, force: true }); |
