Markdown path completions tests use mocked out fs (#153045)

* Markdown path completions tests use mocked out fs

This updates the path completion tests to stop depending on the actual fs and instead use `IMdWorkspace`

* Update remaining tests
This commit is contained in:
Matt Bierner
2022-06-24 16:01:24 -07:00
committed by GitHub
parent 1b570248a5
commit 3b549009fe
8 changed files with 200 additions and 85 deletions

View File

@@ -35,6 +35,19 @@ export class InMemoryMdWorkspace implements IMdWorkspace {
return this._documents.has(resource);
}
public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> {
const files = new Map<string, vscode.FileType>();
const pathPrefix = resource.fsPath + (resource.fsPath.endsWith('/') ? '' : '/');
for (const doc of this._documents.values()) {
const path = doc.uri.fsPath;
if (path.startsWith(pathPrefix)) {
const parts = path.slice(pathPrefix.length).split('/');
files.set(parts[0], parts.length > 1 ? vscode.FileType.Directory : vscode.FileType.File);
}
}
return Array.from(files.entries());
}
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<ITextDocument>();
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;

View File

@@ -10,36 +10,45 @@ import { MdLinkProvider } from '../languageFeatures/documentLinks';
import { MdVsCodePathCompletionProvider } from '../languageFeatures/pathCompletions';
import { noopToken } from '../util/cancellation';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { CURSOR, getCursorPositions, joinLines, workspacePath } from './util';
function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) {
async function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string, workspace?: IMdWorkspace) {
const doc = new InMemoryDocument(resource, fileContents);
const workspace = new InMemoryMdWorkspace([doc]);
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine, workspace, nulLogger);
const provider = new MdVsCodePathCompletionProvider(engine, linkProvider);
const ws = workspace ?? new InMemoryMdWorkspace([doc]);
const linkProvider = new MdLinkProvider(engine, ws, nulLogger);
const provider = new MdVsCodePathCompletionProvider(ws, engine, linkProvider);
const cursorPositions = getCursorPositions(fileContents, doc);
return provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
const completions = await provider.provideCompletionItems(doc, cursorPositions[0], noopToken, {
triggerCharacter: undefined,
triggerKind: vscode.CompletionTriggerKind.Invoke,
});
return completions.sort((a, b) => (a.label as string).localeCompare(b.label as string));
}
suite('Markdown path completion provider', () => {
function assertCompletionsEqual(actual: readonly vscode.CompletionItem[], expected: readonly { label: string; insertText?: string }[]) {
assert.strictEqual(actual.length, expected.length, 'Completion counts should be equal');
setup(async () => {
// These tests assume that the markdown completion provider is already registered
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
});
for (let i = 0; i < actual.length; ++i) {
assert.strictEqual(actual[i].label, expected[i].label, `Completion labels ${i} should be equal`);
if (typeof expected[i].insertText !== 'undefined') {
assert.strictEqual(actual[i].insertText, expected[i].insertText, `Completion insert texts ${i} should be equal`);
}
}
}
suite('Markdown: Path completions', () => {
test('Should not return anything when triggered in empty doc', async () => {
const completions = await getCompletionsAtCursor(workspacePath('new.md'), `${CURSOR}`);
assert.strictEqual(completions.length, 0);
assertCompletionsEqual(completions, []);
});
test('Should return anchor completions', async () => {
@@ -50,9 +59,10 @@ suite('Markdown path completion provider', () => {
`# x y Z`,
));
assert.strictEqual(completions.length, 2);
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has a-b-c anchor completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has x-y-z anchor completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: '#x-y-z' },
]);
});
test('Should not return suggestions for http links', async () => {
@@ -64,53 +74,87 @@ suite('Markdown path completion provider', () => {
`# https:`,
));
assert.strictEqual(completions.length, 0);
assertCompletionsEqual(completions, []);
});
test('Should return relative path suggestions', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return relative path suggestions using ./', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/foo.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return absolute path suggestions using /', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/c.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/${CURSOR}`,
``,
`# A b C`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assert.ok(!completions.some(x => x.label === 'c.md'), 'Should not have c.md from sub folder');
assertCompletionsEqual(completions, [
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should return anchor suggestions in other file', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('b.md'), joinLines(
`# b`,
``,
`[./a](./a)`,
``,
`# header1`,
)),
]);
const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines(
`[](/b.md#${CURSOR}`,
));
), workspace);
assert.ok(completions.some(x => x.label === '#b'), 'Has #b header completion');
assert.ok(completions.some(x => x.label === '#header1'), 'Has #header1 header completion');
assertCompletionsEqual(completions, [
{ label: '#b' },
{ label: '#header1' },
]);
});
test('Should reference links for current file', async () => {
@@ -121,9 +165,10 @@ suite('Markdown path completion provider', () => {
`[ref-2]: bla`,
));
assert.strictEqual(completions.length, 2);
assert.ok(completions.some(x => x.label === 'ref-1'), 'Has ref-1 reference completion');
assert.ok(completions.some(x => x.label === 'ref-2'), 'Has ref-2 reference completion');
assertCompletionsEqual(completions, [
{ label: 'ref-1' },
{ label: 'ref-2' },
]);
});
test('Should complete headers in link definitions', async () => {
@@ -133,67 +178,118 @@ suite('Markdown path completion provider', () => {
`[ref-1]: ${CURSOR}`,
));
assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has #a-b-c header completion');
assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has #x-y-z header completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: '#x-y-z' },
{ label: 'new.md' },
]);
});
test('Should complete relative paths in link definitions', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/c.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`# a B c`,
`[ref-1]: ${CURSOR}`,
));
), workspace);
assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion');
assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion');
assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion');
assertCompletionsEqual(completions, [
{ label: '#a-b-c' },
{ label: 'a.md' },
{ label: 'b.md' },
{ label: 'sub/' },
]);
});
test('Should escape spaces in path names', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub/${CURSOR})`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file%20with%20space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file%20with%20space.md' },
]);
});
test('Should support completions on angle bracket path with spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('sub with space/a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](</sub with space/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has path completion');
assertCompletionsEqual(completions, [
{ label: 'a.md', insertText: 'a.md' },
]);
});
test('Should not escape spaces in path names that use angle brackets', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('sub/file with space.md'), ''),
]);
{
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](<./sub/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file with space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file with space.md' },
]);
}
{
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](<./sub/${CURSOR}>`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file with space.md'), 'Has encoded path completion');
assertCompletionsEqual(completions, [
{ label: 'file with space.md', insertText: 'file with space.md' },
]);
}
});
test('Should complete paths for path with encoded spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[](./sub%20with%20space/${CURSOR})`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
assertCompletionsEqual(completions, [
{ label: 'file.md', insertText: 'file.md' },
]);
});
test('Should complete definition path for path with encoded spaces', async () => {
const workspace = new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('a.md'), ''),
new InMemoryDocument(workspacePath('b.md'), ''),
new InMemoryDocument(workspacePath('sub with space/file.md'), ''),
]);
const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines(
`[def]: ./sub%20with%20space/${CURSOR}`
));
), workspace);
assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space');
assertCompletionsEqual(completions, [
{ label: 'file.md', insertText: 'file.md' },
]);
});
});