Add some tests for quickfixes
This commit is contained in:
@@ -16,6 +16,15 @@ public class QuickfixResolveParams {
|
||||
private Object params;
|
||||
|
||||
|
||||
public QuickfixResolveParams(String type, Object params) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public QuickfixResolveParams() {
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,21 @@ import java.util.Collection;
|
||||
*/
|
||||
public class CollectionUtil {
|
||||
|
||||
/**
|
||||
* Get some element of the collection (will be the first one
|
||||
* found by its iterator, or null if the collection is empty).
|
||||
* <p>
|
||||
* Note that unless the collection is ordered, or has at most
|
||||
* one element, then it may be unpredictable which element
|
||||
* you will get.
|
||||
*/
|
||||
public static <E> E getAny(Collection<E> elements) {
|
||||
for (E e : elements) {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <E> boolean hasElements(Collection<E> c) {
|
||||
return c!=null && !c.isEmpty();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
@@ -140,6 +141,9 @@ public class YamlSchemaProblems {
|
||||
.map(YamlPathSegment::encode)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String fixTitle = missingProps.size()==1
|
||||
? "Add property '"+CollectionUtil.getAny(missingProps)+"'"
|
||||
: "Add properties: "+missingProps;
|
||||
QuickfixData<MissingPropertiesData> fix = new QuickfixData<MissingPropertiesData>(
|
||||
quickfixType,
|
||||
new MissingPropertiesData(
|
||||
@@ -147,7 +151,7 @@ public class YamlSchemaProblems {
|
||||
segments,
|
||||
ImmutableList.copyOf(missingProps)
|
||||
),
|
||||
"Add properties: "+missingProps
|
||||
fixTitle
|
||||
);
|
||||
|
||||
return missingProperty(msg, dc.getDocument(), parent, map)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.languageserver.testharness;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
|
||||
/**
|
||||
* Wrapper for the test harness to refer to and manipulate a
|
||||
* CodeAction.
|
||||
*/
|
||||
public class CodeAction {
|
||||
|
||||
private final Command command;
|
||||
private LanguageServerHarness harness;
|
||||
|
||||
public CodeAction(LanguageServerHarness harness, Command command) {
|
||||
super();
|
||||
this.harness = harness;
|
||||
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command.toString();
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return command.getTitle();
|
||||
}
|
||||
|
||||
public void perform() throws Exception {
|
||||
harness.perform(command);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
@@ -42,6 +43,7 @@ import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.junit.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.CollectionUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@@ -627,4 +629,22 @@ public class Editor {
|
||||
return languageId;
|
||||
}
|
||||
|
||||
public List<CodeAction> getCodeActions(Diagnostic problem) throws Exception {
|
||||
return harness.getCodeActions(document, problem);
|
||||
}
|
||||
|
||||
public CodeAction assertCodeAction(Diagnostic problem) throws Exception {
|
||||
List<CodeAction> actions = getCodeActions(problem);
|
||||
assertEquals("Number of codeActions", 1, actions.size());
|
||||
return actions.get(0);
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return document.getUri();
|
||||
}
|
||||
|
||||
public void assertRawText(String expectedText) throws Exception {
|
||||
assertEquals(expectedText, getRawText());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,17 +18,23 @@ import static org.junit.Assert.assertNotNull;
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.eclipse.lsp4j.ClientCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionParams;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
@@ -50,13 +56,25 @@ import org.eclipse.lsp4j.TextDocumentItem;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncKind;
|
||||
import org.eclipse.lsp4j.TextDocumentSyncOptions;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixResolveParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class LanguageServerHarness {
|
||||
|
||||
@@ -73,6 +91,7 @@ public class LanguageServerHarness {
|
||||
|
||||
private Map<String,TextDocumentInfo> documents = new HashMap<>();
|
||||
private Map<String, PublishDiagnosticsParams> diagnostics = new HashMap<>();
|
||||
private List<Editor> activeEditors = new ArrayList<>();
|
||||
|
||||
|
||||
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory, String defaultLanguageId) {
|
||||
@@ -340,11 +359,13 @@ public class LanguageServerHarness {
|
||||
}
|
||||
|
||||
public Editor newEditor(String contents) throws Exception {
|
||||
return new Editor(this, contents, getDefaultLanguageId());
|
||||
return newEditor(getDefaultLanguageId(), contents);
|
||||
}
|
||||
|
||||
public Editor newEditor(String languageId, String contents) throws Exception {
|
||||
return new Editor(this, contents, languageId);
|
||||
public synchronized Editor newEditor(String languageId, String contents) throws Exception {
|
||||
Editor editor = new Editor(this, contents, languageId);
|
||||
activeEditors.add(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
public synchronized TextDocumentInfo createWorkingCopy(String contents, String languageId) throws Exception {
|
||||
@@ -406,4 +427,71 @@ public class LanguageServerHarness {
|
||||
return server.getTextDocumentService().definition(params).get();
|
||||
}
|
||||
|
||||
public List<CodeAction> getCodeActions(TextDocumentInfo doc, Diagnostic problem) throws Exception {
|
||||
CodeActionContext context = new CodeActionContext(ImmutableList.of(problem));
|
||||
List<? extends Command> actions =
|
||||
server.getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
|
||||
return actions.stream()
|
||||
.map((command) -> new CodeAction(this, command))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public void perform(Command command) throws Exception {
|
||||
switch (command.getCommand()) {
|
||||
case "sts.quickfix":
|
||||
List<Object> args = command.getArguments();
|
||||
assertEquals(2, args.size());
|
||||
//Note convert the value to a 'typeless' Object becaus that is more representative on how it will be
|
||||
// received when we get it in a real client/server setting (i.e. parsed from json).
|
||||
Object untypedParams = mapper.convertValue(args.get(1), Object.class);
|
||||
perform(server.quickfixResolve(new QuickfixResolveParams((String)args.get(0), untypedParams)).get());
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown command: "+command);
|
||||
}
|
||||
}
|
||||
|
||||
private void perform(WorkspaceEdit workspaceEdit) throws Exception {
|
||||
Assert.isNull("Versioned WorkspaceEdits not supported", workspaceEdit.getDocumentChanges());
|
||||
for (Entry<String, List<TextEdit>> entry : workspaceEdit.getChanges().entrySet()) {
|
||||
String uri = entry.getKey();
|
||||
TextDocumentInfo document = documents.get(uri);
|
||||
assertNotNull("Can't apply edits to non-existing document: "+uri, document);
|
||||
|
||||
TextDocument workingDocument = new TextDocument(uri, document.getLanguageId());
|
||||
workingDocument.setText(document.getText());
|
||||
DocumentEdits edits = new DocumentEdits(workingDocument);
|
||||
for (TextEdit edit : entry.getValue()) {
|
||||
Range range = edit.getRange();
|
||||
edits.replace(document.toOffset(range.getStart()), document.toOffset(range.getEnd()), edit.getNewText());
|
||||
}
|
||||
edits.apply(workingDocument);
|
||||
Editor editor = getOpenEditor(uri);
|
||||
if (editor!=null) {
|
||||
editor.setRawText(workingDocument.get());
|
||||
} else {
|
||||
changeDocument(uri, workingDocument.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Editor getOpenEditor(String uri) {
|
||||
List<Editor> editors = getOpenEditors(uri);
|
||||
if (editors.isEmpty()) {
|
||||
return null;
|
||||
} else if (editors.size()>1) {
|
||||
throw new IllegalStateException("Multiple active editors on the same uri. The harness doesn't handle that yet!");
|
||||
}
|
||||
return editors.get(0);
|
||||
}
|
||||
|
||||
private synchronized List<Editor> getOpenEditors(String uri) {
|
||||
return activeEditors.stream()
|
||||
.filter((editor) -> uri.equals(editor.getUri()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
|
||||
import org.springframework.ide.vscode.commons.util.IOUtil;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
|
||||
|
||||
@@ -42,6 +43,54 @@ public class ConcourseEditorTest {
|
||||
harness.intialize(null);
|
||||
}
|
||||
|
||||
@Test public void addSingleRequiredPropertiesQuickfix() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" source:\n" +
|
||||
" username: someone\n" +
|
||||
"# Confuse"
|
||||
);
|
||||
Diagnostic problem = editor.assertProblems("-|'type' is required").get(0);
|
||||
CodeAction quickfix = editor.assertCodeAction(problem);
|
||||
assertEquals("Add property 'type'", quickfix.getLabel());
|
||||
quickfix.perform();
|
||||
|
||||
editor.assertRawText(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" source:\n" +
|
||||
" username: someone\n" +
|
||||
" type: \n" +
|
||||
"# Confuse"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void addMultipleRequiredPropertiesQuickfix() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: pool\n" +
|
||||
" source:\n" +
|
||||
" username: someone\n"
|
||||
);
|
||||
Diagnostic problem = editor.assertProblems("source|[branch, pool, uri] are required").get(0);
|
||||
CodeAction quickfix = editor.assertCodeAction(problem);
|
||||
assertEquals("Add properties: [branch, pool, uri]", quickfix.getLabel());
|
||||
quickfix.perform();
|
||||
|
||||
editor.assertRawText(
|
||||
"resources:\n" +
|
||||
"- name: foo\n" +
|
||||
" type: pool\n" +
|
||||
" source:\n" +
|
||||
" username: someone\n" +
|
||||
" branch: \n" +
|
||||
" pool: \n" +
|
||||
" uri: \n"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void testReconcileCatchesParseError() throws Exception {
|
||||
Editor editor = harness.newEditor(
|
||||
"somemap: val\n"+
|
||||
|
||||
@@ -69,7 +69,6 @@ public class TimeOfDayParserTest {
|
||||
"2359"
|
||||
};
|
||||
for (String string : examples) {
|
||||
System.out.println(string);
|
||||
parser.parse(string);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +77,8 @@ public class TimeOfDayParserTest {
|
||||
public void badExamples() {
|
||||
does_not_parse("arbirary garbage");
|
||||
does_not_parse("3:04 PM -0700 extra");
|
||||
does_not_parse("extra 3:04 PM -0700 extra");
|
||||
does_not_parse("extra 3:04 PM -0700");
|
||||
}
|
||||
|
||||
private void does_not_parse(String string) {
|
||||
|
||||
Reference in New Issue
Block a user