generated from redhat-developer/new-project-template
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Java-specific unit tests for server-side on-type formatting.
- Loading branch information
Showing
3 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
.../com/redhat/devtools/lsp4ij/features/formatting/JavaClientSideFormatOnCloseBraceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2025 Red Hat, Inc. | ||
* Distributed under license by Red Hat, Inc. All rights reserved. | ||
* This program is made available under the terms of the | ||
* Eclipse Public License v2.0 which accompanies this distribution, | ||
* and is available at http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
******************************************************************************/ | ||
|
||
package com.redhat.devtools.lsp4ij.features.formatting; | ||
|
||
import com.redhat.devtools.lsp4ij.fixtures.LSPServerSideOnTypeFormattingFixtureTestCase; | ||
|
||
/** | ||
* Java-based server-side on-type formatting tests for format-on-close brace. Note that jdtls' support for on-type | ||
* formatting does not seem to work well enough for format-on-statement terminator or newline even though those are | ||
* included as supported on-type formatting characters in the language server initialization response. Both of those | ||
* characters seem to yield empty text edit lists to be applied. | ||
*/ | ||
public class JavaClientSideFormatOnCloseBraceTest extends LSPServerSideOnTypeFormattingFixtureTestCase { | ||
|
||
private static final String TEST_FILE_NAME = "Test.java"; | ||
|
||
public JavaClientSideFormatOnCloseBraceTest() { | ||
super("*.java"); | ||
// On-type formatting trigger characters for jdtls | ||
setTriggerCharacters(";", "\n", "}"); | ||
} | ||
|
||
public void testSimple() { | ||
assertOnTypeFormatting( | ||
TEST_FILE_NAME, | ||
// No language injection here because there are syntax errors | ||
""" | ||
public class Hello { | ||
public static void main(String[] args) { | ||
System.out.println("Hello, world."); | ||
// type } | ||
} | ||
""", | ||
// language=java | ||
""" | ||
public class Hello { | ||
public static void main(String[] args) { | ||
System.out.println("Hello, world."); | ||
} | ||
} | ||
""", | ||
// language=json | ||
""" | ||
[ | ||
{ | ||
"range": { | ||
"start": { | ||
"line": 1, | ||
"character": 44 | ||
}, | ||
"end": { | ||
"line": 2, | ||
"character": 0 | ||
} | ||
}, | ||
"newText": "\\n " | ||
} | ||
] | ||
""" | ||
); | ||
} | ||
|
||
public void testComplex() { | ||
assertOnTypeFormatting( | ||
TEST_FILE_NAME, | ||
// No language injection here because there are syntax errors | ||
""" | ||
public class Hello { | ||
public static void main(String[] args) { | ||
System.out.println("Hello, world."); | ||
} | ||
// type } | ||
""", | ||
// language=java | ||
""" | ||
public class Hello { | ||
public static void main(String[] args) { | ||
System.out.println("Hello, world."); | ||
} | ||
} | ||
""", | ||
// language=json | ||
""" | ||
[ | ||
{ | ||
"range": { | ||
"start": { | ||
"line": 0, | ||
"character": 20 | ||
}, | ||
"end": { | ||
"line": 1, | ||
"character": 0 | ||
} | ||
}, | ||
"newText": "\\n " | ||
}, | ||
{ | ||
"range": { | ||
"start": { | ||
"line": 1, | ||
"character": 40 | ||
}, | ||
"end": { | ||
"line": 2, | ||
"character": 0 | ||
} | ||
}, | ||
"newText": "\\n " | ||
}, | ||
{ | ||
"range": { | ||
"start": { | ||
"line": 2, | ||
"character": 36 | ||
}, | ||
"end": { | ||
"line": 3, | ||
"character": 0 | ||
} | ||
}, | ||
"newText": "\\n " | ||
} | ||
] | ||
""" | ||
); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
...ava/com/redhat/devtools/lsp4ij/fixtures/LSPServerSideOnTypeFormattingFixtureTestCase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2025 Red Hat, Inc. | ||
* Distributed under license by Red Hat, Inc. All rights reserved. | ||
* This program is made available under the terms of the | ||
* Eclipse Public License v2.0 which accompanies this distribution, | ||
* and is available at http://www.eclipse.org/legal/epl-v20.html | ||
* | ||
* Contributors: | ||
* Red Hat, Inc. - initial API and implementation | ||
******************************************************************************/ | ||
|
||
package com.redhat.devtools.lsp4ij.fixtures; | ||
|
||
import com.google.gson.reflect.TypeToken; | ||
import com.intellij.openapi.editor.CaretModel; | ||
import com.intellij.openapi.editor.Editor; | ||
import com.intellij.openapi.project.Project; | ||
import com.intellij.openapi.util.Pair; | ||
import com.intellij.psi.PsiFile; | ||
import com.intellij.testFramework.EditorTestUtil; | ||
import com.intellij.util.containers.ContainerUtil; | ||
import com.redhat.devtools.lsp4ij.JSONUtils; | ||
import com.redhat.devtools.lsp4ij.LanguageServerItem; | ||
import com.redhat.devtools.lsp4ij.LanguageServiceAccessor; | ||
import com.redhat.devtools.lsp4ij.mock.MockLanguageServer; | ||
import org.eclipse.lsp4j.DocumentOnTypeFormattingOptions; | ||
import org.eclipse.lsp4j.TextEdit; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Collections; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Base class test case for server-side on-type formatting tests by emulating LSP 'textDocument/onTypeFormatting' responses. | ||
*/ | ||
public abstract class LSPServerSideOnTypeFormattingFixtureTestCase extends LSPCodeInsightFixtureTestCase { | ||
|
||
private static final Pattern TYPE_INFO_PATTERN = Pattern.compile("(?ms)//\\s*type\\s*(\\S)[\\t ]*"); | ||
|
||
private List<String> triggerCharacters = new LinkedList<>(); | ||
|
||
public LSPServerSideOnTypeFormattingFixtureTestCase(String... fileNamePatterns) { | ||
super(fileNamePatterns); | ||
} | ||
|
||
protected void setTriggerCharacters(@NotNull String... triggerCharacters) { | ||
ContainerUtil.addAllNotNull(this.triggerCharacters, triggerCharacters); | ||
} | ||
|
||
/** | ||
* Asserts that <code>fileBodyBefore</code> is formatted into <code>fileBodyAfter</code> when the typing imperative | ||
* embedded in <code>fileBodyBefore</code> is applied. The typing imperative is just a simple comment of the form | ||
* <code>// type <character></code> at the offset at which <code><character></code> should be typed. | ||
* A mock LSP response must be provided for <code>textDocument/onTypeFormatting</code>. | ||
* | ||
* @param fileName the file name | ||
* @param fileBodyBefore the file body before including the embedded typing imperative comment | ||
* @param fileBodyAfter the file body after the character has been typed and formatting applied | ||
* @param mockOnTypeFormattingJson the mock on-type formatting JSON response | ||
*/ | ||
protected void assertOnTypeFormatting(@NotNull String fileName, | ||
@NotNull String fileBodyBefore, | ||
@NotNull String fileBodyAfter, | ||
@NotNull String mockOnTypeFormattingJson) { | ||
MockLanguageServer.INSTANCE.setTimeToProceedQueries(100); | ||
|
||
List<TextEdit> mockTextEdits = JSONUtils.getLsp4jGson().fromJson(mockOnTypeFormattingJson, new TypeToken<List<TextEdit>>() { | ||
}.getType()); | ||
MockLanguageServer.INSTANCE.setFormattingTextEdits(mockTextEdits); | ||
|
||
Project project = myFixture.getProject(); | ||
PsiFile file = myFixture.configureByText(fileName, removeTypeInfo(fileBodyBefore)); | ||
Editor editor = myFixture.getEditor(); | ||
|
||
// Initialize the language server | ||
List<LanguageServerItem> languageServers = new LinkedList<>(); | ||
try { | ||
ContainerUtil.addAllNotNull(languageServers, LanguageServiceAccessor.getInstance(project) | ||
.getLanguageServers(file.getVirtualFile(), null, null) | ||
.get(5000, TimeUnit.MILLISECONDS)); | ||
} catch (Exception e) { | ||
fail(e.getMessage()); | ||
} | ||
|
||
// Configure the language server for client-side on-type formatting | ||
LanguageServerItem languageServer = ContainerUtil.getFirstItem(languageServers); | ||
assertNotNull(languageServer); | ||
|
||
// Enable on-type formatting with the specified trigger characters | ||
String firstTriggerCharacter = ContainerUtil.getFirstItem(triggerCharacters); | ||
assertNotNull("At least one trigger character must be specified.", firstTriggerCharacter); | ||
List<String> moreTriggerCharacters = triggerCharacters.size() > 1 ? triggerCharacters.subList(1, triggerCharacters.size()) : Collections.emptyList(); | ||
languageServer.getServerCapabilities().setDocumentOnTypeFormattingProvider(new DocumentOnTypeFormattingOptions(firstTriggerCharacter, moreTriggerCharacters)); | ||
|
||
EditorTestUtil.buildInitialFoldingsInBackground(editor); | ||
|
||
// Derive the offset and character that should be typed | ||
Pair<Integer, Character> typeInfo = getTypeInfo(fileBodyBefore); | ||
assertNotNull("No type information found in file body before.", typeInfo); | ||
int offset = typeInfo.getFirst(); | ||
char character = typeInfo.getSecond(); | ||
|
||
// Move to the offset and type the character | ||
CaretModel caretModel = editor.getCaretModel(); | ||
caretModel.moveToOffset(offset); | ||
EditorTestUtil.performTypingAction(editor, character); | ||
|
||
// Confirm that the file body has been reformatted as expected | ||
assertEquals(fileBodyAfter, editor.getDocument().getText()); | ||
} | ||
|
||
@NotNull | ||
private String removeTypeInfo(@NotNull String fileBodyBefore) { | ||
return TYPE_INFO_PATTERN.matcher(fileBodyBefore).replaceFirst(""); | ||
} | ||
|
||
@Nullable | ||
private Pair<Integer, Character> getTypeInfo(@NotNull String fileBodyBefore) { | ||
Matcher typeInfoMatcher = TYPE_INFO_PATTERN.matcher(fileBodyBefore); | ||
if (typeInfoMatcher.find()) { | ||
int offset = typeInfoMatcher.start(); | ||
char character = typeInfoMatcher.group(1).charAt(0); | ||
return Pair.create(offset, character); | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters