Skip to content

Commit

Permalink
test: Added unit tests for LSP-based code folding and the code block …
Browse files Browse the repository at this point in the history
…provider (issue 673) (#679)

Create tests for LSP code folding 

Fixes #673
  • Loading branch information
SCWells72 authored Dec 10, 2024
1 parent 254cf2e commit 7dd0dec
Show file tree
Hide file tree
Showing 7 changed files with 500 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.intellij.lang.ASTNode;
import com.intellij.lang.folding.CustomFoldingBuilder;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.TextRange;
Expand Down Expand Up @@ -62,8 +63,8 @@ public class LSPFoldingRangeBuilder extends CustomFoldingBuilder {
protected void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
@NotNull PsiElement root,
@NotNull Document document, boolean quick) {
// if quick flag is set, we do nothing here
if (quick) {
// if quick flag is set and not testing, we do nothing here
if (quick && !ApplicationManager.getApplication().isUnitTestMode()) {
return;
}

Expand Down Expand Up @@ -219,6 +220,7 @@ protected boolean isRegionCollapsedByDefault(@NotNull ASTNode node) {

@Override
public boolean isDumbAware() {
return false;
// Allow folding in dumb mode only during unit testing
return ApplicationManager.getApplication().isUnitTestMode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*******************************************************************************
* Copyright (c) 2024 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.foldingRange;

import com.redhat.devtools.lsp4ij.fixtures.LSPCodeBlockProviderFixtureTestCase;

/**
* Selection range tests by emulating LSP 'textDocument/foldingRange' responses from the typescript-language-server.
*/
public class TypeScriptCodeBlockProviderTest extends LSPCodeBlockProviderFixtureTestCase {

public TypeScriptCodeBlockProviderTest() {
super("*.ts");
}

public void testCodeBlocks() {
// language=json
String mockFoldingRangesJson = """
[
{
"startLine": 0,
"endLine": 3
},
{
"startLine": 1,
"endLine": 2
}
]
""";

assertCodeBlock(
"demo.ts",
"""
export class Demo {
demo() {<start>
<caret>console.log('demo');
<end>}
}
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class Demo {<start>
<caret>demo() {
console.log('demo');
}
<end>}
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class Demo {<caret><start>
demo() {
console.log('demo');
}
<end>}
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class Demo <caret>{<start>
demo() {
console.log('demo');
}
<end>}
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class Demo {<start>
demo() {
console.log('demo');
}
<end><caret>}
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class Demo {<start>
demo() {
console.log('demo');
}
<end>}<caret>
""",
mockFoldingRangesJson
);

assertCodeBlock(
"demo.ts",
"""
export class <caret><start><end>Demo {
demo() {
console.log('demo');
}
}
""",
mockFoldingRangesJson
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2024 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.foldingRange;

import com.redhat.devtools.lsp4ij.fixtures.LSPFoldingRangeFixtureTestCase;

/**
* Selection range tests by emulating LSP 'textDocument/foldingRange' responses from the typescript-language-server.
*/
public class TypeScriptFoldingRangeTest extends LSPFoldingRangeFixtureTestCase {

public TypeScriptFoldingRangeTest() {
super("*.ts");
}

public void testFoldingRanges() {
assertFoldingRanges(
"demo.ts",
"""
export class Demo {<start1>
demo() {<start2>
console.log('demo');
<end2>}
<end1>}
""",
// language=json
"""
[
{
"startLine": 0,
"endLine": 3
},
{
"startLine": 1,
"endLine": 2
}
]
"""
);
}

public void testFoldingRanges_collapsedText() {
assertFoldingRanges(
"demo.ts",
"""
export class Demo {<start1>
demo() {<start2>
console.log('demo');
<end2>}
<end1>}
""",
// language=json
"""
[
{
"startLine": 0,
"endLine": 3,
"collapsedText": "classBody"
},
{
"startLine": 1,
"endLine": 2,
"collapsedText": "methodBody"
}
]
"""
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*******************************************************************************
* Copyright (c) 2024 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.codeInsight.editorActions.CodeBlockUtil;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.EditorTestUtil;
import com.redhat.devtools.lsp4ij.JSONUtils;
import com.redhat.devtools.lsp4ij.LanguageServiceAccessor;
import com.redhat.devtools.lsp4ij.mock.MockLanguageServer;
import org.eclipse.lsp4j.FoldingRange;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* Base class test case to test the 'codeBlockProvider' based on LSP 'textDocument/codeBlock' feature.
*/
public abstract class LSPCodeBlockProviderFixtureTestCase extends LSPCodeInsightFixtureTestCase {

private static final String CARET_TOKEN = "<caret>";
private static final String START_TOKEN = "<start>";
private static final String END_TOKEN = "<end>";

protected LSPCodeBlockProviderFixtureTestCase(String... fileNamePatterns) {
super(fileNamePatterns);
}

protected void assertCodeBlock(@NotNull String fileName,
@NotNull String fileBody,
@NotNull String mockFoldingRangesJson) {
MockLanguageServer.INSTANCE.setTimeToProceedQueries(100);
List<FoldingRange> mockFoldingRanges = JSONUtils.getLsp4jGson().fromJson(mockFoldingRangesJson, new TypeToken<List<FoldingRange>>() {
}.getType());
MockLanguageServer.INSTANCE.setFoldingRanges(mockFoldingRanges);

Project project = myFixture.getProject();
PsiFile file = myFixture.configureByText(fileName, stripTokens(fileBody));
Editor editor = myFixture.getEditor();

// Initialize the language server
try {
LanguageServiceAccessor.getInstance(project)
.getLanguageServers(file.getVirtualFile(), null, null)
.get(5000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
fail(e.getMessage());
}

EditorTestUtil.buildInitialFoldingsInBackground(editor);

CaretModel caretModel = editor.getCaretModel();

// Derive the caret, start, and end offsets from tokens in the file body
Trinity<Integer, Integer, Integer> offsets = getOffsets(fileBody);
int caretOffset = offsets.getFirst();
int startOffset = offsets.getSecond();
int endOffset = offsets.getThird();

caretModel.moveToOffset(caretOffset);
CodeBlockUtil.moveCaretToCodeBlockStart(project, editor, false);
assertEquals(startOffset, caretModel.getOffset());

caretModel.moveToOffset(caretOffset);
CodeBlockUtil.moveCaretToCodeBlockEnd(project, editor, false);
assertEquals(endOffset, caretModel.getOffset());
}

@NotNull
private static String stripTokens(@NotNull String fileBody) {
return fileBody
.replace(CARET_TOKEN, "")
.replace(START_TOKEN, "")
.replace(END_TOKEN, "");
}

@NotNull
private static Trinity<@NotNull Integer, @NotNull Integer, @NotNull Integer> getOffsets(@NotNull String fileBody) {
// Gather the raw token offsets
int rawCaretOffset = fileBody.indexOf(CARET_TOKEN);
assertFalse("No " + CARET_TOKEN + " found.", rawCaretOffset == -1);
int rawStartOffset = fileBody.indexOf(START_TOKEN);
assertFalse("No " + START_TOKEN + " found.", rawStartOffset == -1);
int rawEndOffset = fileBody.indexOf(END_TOKEN);
assertFalse("No " + END_TOKEN + " found.", rawEndOffset == -1);

// Adjust final offsets as appropriate based on relative token positioning
int caretOffset = rawCaretOffset;
if (rawCaretOffset > rawStartOffset) caretOffset -= START_TOKEN.length();
if (rawCaretOffset > rawEndOffset) caretOffset -= END_TOKEN.length();
int startOffset = rawStartOffset;
if (rawStartOffset > rawCaretOffset) startOffset -= CARET_TOKEN.length();
if (rawStartOffset > rawEndOffset) startOffset -= END_TOKEN.length();
int endOffset = rawEndOffset;
if (rawEndOffset > rawCaretOffset) endOffset -= CARET_TOKEN.length();
if (rawEndOffset > rawStartOffset) endOffset -= START_TOKEN.length();

return Trinity.create(caretOffset, startOffset, endOffset);
}
}
Loading

0 comments on commit 7dd0dec

Please sign in to comment.