mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
[mod-commands] Testing: declarative testing of multi-step actions
GitOrigin-RevId: d8c195494ae7973a44c1daf707fc38e6f76a7191
This commit is contained in:
committed by
intellij-monorepo-bot
parent
be9a7b3ed9
commit
9ab2a2cd01
@@ -0,0 +1,16 @@
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference|->Delete possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
AClass getA() {
|
||||
return null;
|
||||
}
|
||||
static int fff;
|
||||
}
|
||||
|
||||
class acc {
|
||||
int f() {
|
||||
AClass a = null;
|
||||
return AClass.fff;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference" "true-preview"
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference|->Extract possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Access static 'R.rr' via class 'R' reference" "true-preview"
|
||||
// "Access static 'R.rr' via class 'R' reference|->Extract possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference|->Delete possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
AClass getA() {
|
||||
return null;
|
||||
}
|
||||
static int fff;
|
||||
}
|
||||
|
||||
class acc {
|
||||
int f() {
|
||||
AClass a = null;
|
||||
return <caret>a.getA().fff;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference" "true-preview"
|
||||
// "Access static 'AClass.fff' via class 'AClass' reference|->Extract possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Access static 'R.rr' via class 'R' reference" "true-preview"
|
||||
// "Access static 'R.rr' via class 'R' reference|->Extract possible side effects" "true-preview"
|
||||
|
||||
class AClass
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace static import with qualified access to Arrays" "true-preview"
|
||||
// "Replace static import with qualified access to Arrays|->Replace this occurrence and keep the import" "true-preview"
|
||||
import java.util.Arrays;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace static import with qualified access to Arrays" "true-preview"
|
||||
// "Replace static import with qualified access to Arrays|->Replace all and delete the import" "true-preview"
|
||||
import java.util.Arrays;
|
||||
|
||||
class Test {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Replace static import with qualified access to Arrays|->Replace this occurrence and keep the import" "true-preview"
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
class Test {
|
||||
public void sendMessage(String... destinationAddressNames) {
|
||||
s<caret>ort(destinationAddressNames);
|
||||
asList(destinationAddressNames)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace static import with qualified access to Arrays" "true-preview"
|
||||
// "Replace static import with qualified access to Arrays|->Replace all and delete the import" "true-preview"
|
||||
import static java.util.Arrays.*;
|
||||
|
||||
class Test {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Extract Set from comparison chain" "true-preview"
|
||||
// "Extract Set from comparison chain|->Replace all occurrences" "true-preview"
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Extract Set from comparison chain" "true-preview"
|
||||
// "Extract Set from comparison chain|->Replace all occurrences" "true-preview"
|
||||
|
||||
public class Test {
|
||||
enum Status {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// "Extract Set from comparison chain" "true-preview"
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Test {
|
||||
private static final Set<Status> STATUSES = Collections.unmodifiableSet(EnumSet.of(Status.VALID, Status.PENDING));
|
||||
|
||||
enum Status {
|
||||
VALID, PENDING, INVALID, UNKNOWN;
|
||||
}
|
||||
|
||||
void test1(Status status) {
|
||||
if(STATUSES.contains(status)) {
|
||||
System.out.println("ok");
|
||||
}
|
||||
}
|
||||
|
||||
static class Another {
|
||||
static final String STATUSES = "";
|
||||
|
||||
void test2(Status st) {
|
||||
if(st == null || Status.PENDING == st || Status.VALID == st || Math.random() > 0.5) {
|
||||
System.out.println("Replace here as well");
|
||||
}
|
||||
}
|
||||
|
||||
void test3(Status st2) {
|
||||
if(st2 == Status.VALID || st2 == Status.PENDING || st2 == Status.UNKNOWN) {
|
||||
System.out.println("Do not replace as we test three statuses");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace with '0'" "true-preview"
|
||||
// "Replace with '0'|->Extract possible side effects" "true-preview"
|
||||
import java.util.stream.*;
|
||||
|
||||
class Test {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// "Replace with '0'" "true-preview"
|
||||
// "Replace with '0'|->Extract possible side effects" "true-preview"
|
||||
import java.util.stream.*;
|
||||
|
||||
class Test {
|
||||
|
||||
@@ -4,22 +4,9 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstance;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AccessStaticViaInstanceTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
if (getTestName(false).endsWith("SideEffect.java")) {
|
||||
UiInterceptors.register(new ChooserInterceptor(List.of("Extract possible side effects", "Delete possible side effects"),
|
||||
"Extract possible side effects"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[] {new AccessStaticViaInstance()};
|
||||
|
||||
@@ -6,27 +6,14 @@ import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.ConstantValueInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ReplaceWithConstantValueFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new ConstantValueInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
if (getTestName(false).contains("SideEffect")) {
|
||||
UiInterceptors.register(new ChooserInterceptor(List.of("Extract possible side effects", "Delete possible side effects"),
|
||||
"Extract possible side effects"));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
|
||||
@@ -8,16 +8,6 @@ import com.intellij.ui.UiInterceptors;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpandStaticImportActionTest extends LightIntentionActionTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
if (getTestName(false).contains("Multiple")) {
|
||||
UiInterceptors.register(new ChooserInterceptor(List.of("Replace this occurrence and keep the import",
|
||||
"Replace all and delete the import"),
|
||||
"Replace all and delete the import"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/expandStaticImport";
|
||||
|
||||
@@ -23,16 +23,6 @@ import com.intellij.ui.UiInterceptors;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtractSetFromComparisonChainActionTest extends LightIntentionActionTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
if (getTestName(false).equals("Duplicate.java")) {
|
||||
UiInterceptors.register(new ChooserInterceptor(List.of(
|
||||
"Replace only this occurrence",
|
||||
"Replace all occurrences"), "Replace all occurrences"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LanguageLevel getDefaultLanguageLevel() {
|
||||
return LanguageLevel.JDK_1_8;
|
||||
|
||||
@@ -44,7 +44,8 @@ abstract class LightQuickFixParameterizedTestCase5(projectDescriptor: LightProje
|
||||
fun parameterized(fileName: String) {
|
||||
val filePath = "/" + LightQuickFixTestCase.BEFORE_PREFIX + fileName
|
||||
val file = fixture.configureByFile(filePath)
|
||||
val action = runReadAction { ActionHint.parse(file, file.text) }.findAndCheck(fixture.availableIntentions) {
|
||||
val (hint, context) = runReadAction { ActionHint.parse(file, file.text) to fixture.actionContext }
|
||||
val action = hint.findAndCheck(fixture.availableIntentions, context) {
|
||||
"""
|
||||
Test: ${getRelativePath() + filePath}
|
||||
Language level: ${PsiUtil.getLanguageLevel(fixture.project)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewPopupUpdateProcessor;
|
||||
import com.intellij.modcommand.ModCommandAction.ActionContext;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
@@ -92,6 +93,7 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase
|
||||
@NotNull String testName,
|
||||
@NotNull QuickFixTestCase quickFix) throws Exception {
|
||||
IntentionAction action = actionHint.findAndCheck(quickFix.getAvailableActions(),
|
||||
ActionContext.from(getEditor(), getFile()),
|
||||
() -> getTestInfo(testFullPath, quickFix));
|
||||
if (action != null) {
|
||||
String text = action.getText();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.quickFix;
|
||||
|
||||
import com.intellij.codeInsight.intention.CommonIntentionAction;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.IntentionActionDelegate;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
@@ -8,6 +9,10 @@ import com.intellij.codeInspection.ex.QuickFixWrapper;
|
||||
import com.intellij.lang.Commenter;
|
||||
import com.intellij.lang.LanguageCommenters;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.modcommand.ModChooseAction;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.modcommand.ModCommandAction.ActionContext;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -78,6 +83,8 @@ public final class ActionHint {
|
||||
/**
|
||||
* Finds the action which matches this ActionHint and returns it or returns null
|
||||
* if this ActionHint asserts that no action should be present.
|
||||
* <p>
|
||||
* Use {@link #findAndCheck(Collection, ActionContext, Supplier)} if you expect multistep action
|
||||
*
|
||||
* @param actions actions collection to search inside
|
||||
* @param infoSupplier a supplier which provides additional info which will be appended to exception message if check fails
|
||||
@@ -86,13 +93,57 @@ public final class ActionHint {
|
||||
*/
|
||||
@Nullable
|
||||
public IntentionAction findAndCheck(@NotNull Collection<? extends IntentionAction> actions, @NotNull Supplier<String> infoSupplier) {
|
||||
IntentionAction result = ContainerUtil.find(actions, t -> {
|
||||
String text = t.getText();
|
||||
return myExactMatch ? text.equals(myExpectedText) : text.startsWith(myExpectedText);
|
||||
});
|
||||
return findAndCheck(actions, null, infoSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the action which matches this ActionHint and returns it or returns null
|
||||
* if this ActionHint asserts that no action should be present.
|
||||
*
|
||||
* @param actions actions collection to search inside
|
||||
* @param context action execution context
|
||||
* @param infoSupplier a supplier which provides additional info which will be appended to exception message if check fails
|
||||
* @return the action or null
|
||||
* @throws AssertionError if no action is found, but it should present, or if action is found, but it should not present.
|
||||
*/
|
||||
@Nullable
|
||||
public IntentionAction findAndCheck(@NotNull Collection<? extends IntentionAction> actions,
|
||||
@Nullable ActionContext context,
|
||||
@NotNull Supplier<String> infoSupplier) {
|
||||
String[] steps = myExpectedText.split("\\|->");
|
||||
CommonIntentionAction found = null;
|
||||
Collection<? extends CommonIntentionAction> commonActions = actions;
|
||||
for (int i = 0; i < steps.length; i++) {
|
||||
String curStep = steps[i];
|
||||
found = ContainerUtil.find(commonActions, t -> {
|
||||
String text = getActionText(context, t);
|
||||
return myExactMatch ? text.equals(curStep) : text.startsWith(curStep);
|
||||
});
|
||||
if (i == steps.length - 1) break;
|
||||
if (context == null) {
|
||||
fail("Action context is not supplied");
|
||||
}
|
||||
if (found == null) {
|
||||
fail(exceptionHeader(curStep) + " not found\nAvailable actions: " +
|
||||
commonActions.stream().map(act -> getActionText(context, act)).collect(Collectors.joining(", ", "[", "]\n")) +
|
||||
infoSupplier.get());
|
||||
}
|
||||
ModCommandAction action = found.asModCommandAction();
|
||||
if (action == null) {
|
||||
fail(exceptionHeader(curStep) + " is not ModCommandAction");
|
||||
}
|
||||
ModCommand command = action.perform(context);
|
||||
if (!(command instanceof ModChooseAction chooseAction)) {
|
||||
fail(exceptionHeader(curStep) + " does not produce a chooser");
|
||||
return null;
|
||||
}
|
||||
commonActions = chooseAction.actions();
|
||||
}
|
||||
IntentionAction result = found == null ? null : found.asIntention();
|
||||
String lastStep = steps[steps.length - 1];
|
||||
if(myShouldPresent) {
|
||||
if(result == null) {
|
||||
fail(exceptionHeader() + " not found\nAvailable actions: " +
|
||||
fail(exceptionHeader(lastStep) + " not found\nAvailable actions: " +
|
||||
actions.stream().map(IntentionAction::getText).collect(Collectors.joining(", ", "[", "]\n")) +
|
||||
infoSupplier.get());
|
||||
}
|
||||
@@ -100,25 +151,39 @@ public final class ActionHint {
|
||||
result = IntentionActionDelegate.unwrap(result);
|
||||
ProblemHighlightType actualType = QuickFixWrapper.getHighlightType(result);
|
||||
if (actualType == null) {
|
||||
fail(exceptionHeader() + " is not a LocalQuickFix, but " + result.getClass().getName() +
|
||||
fail(exceptionHeader(lastStep) + " is not a LocalQuickFix, but " + result.getClass().getName() +
|
||||
"\nExpected LocalQuickFix with ProblemHighlightType=" + myHighlightType + "\n" +
|
||||
infoSupplier.get());
|
||||
}
|
||||
if(actualType != myHighlightType) {
|
||||
fail(exceptionHeader() + " has wrong ProblemHighlightType.\nExpected: " + myHighlightType +
|
||||
fail(exceptionHeader(lastStep) + " has wrong ProblemHighlightType.\nExpected: " + myHighlightType +
|
||||
"\nActual: " + actualType + "\n" + infoSupplier.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(result != null) {
|
||||
fail(exceptionHeader() + " is present, but should not\n" + infoSupplier.get());
|
||||
fail(exceptionHeader(lastStep) + " is present, but should not\n" + infoSupplier.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static @NotNull String getActionText(@Nullable ActionContext context, CommonIntentionAction t) {
|
||||
if (t instanceof IntentionAction intention) {
|
||||
return intention.getText();
|
||||
}
|
||||
if (!(t instanceof ModCommandAction action)) {
|
||||
throw new AssertionError("Action is not ModCommandAction: " + t);
|
||||
}
|
||||
if (context == null) {
|
||||
fail("Context is not specified for ModCommandAction");
|
||||
}
|
||||
ModCommandAction.Presentation presentation = action.getPresentation(context);
|
||||
return presentation == null ? "(unavailable) " + action.getFamilyName() : presentation.name();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String exceptionHeader() {
|
||||
return "Action with " + (myExactMatch ? "text" : "prefix") + " '" + myExpectedText + "'";
|
||||
private String exceptionHeader(String text) {
|
||||
return "Action with " + (myExactMatch ? "text" : "prefix") + " '" + text + "'";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -131,7 +196,7 @@ public final class ActionHint {
|
||||
* <p>
|
||||
* Currently the following syntax is supported:
|
||||
* </p>
|
||||
* {@code // "quick-fix name or intention text" "true|false|<ProblemHighlightType>"}
|
||||
* {@code // "quick-fix name or intention text[|->next step]" "true|false|<ProblemHighlightType>[-preview]"}
|
||||
* <p>
|
||||
* (replace // with line comment prefix in the corresponding language if necessary).
|
||||
* If {@link ProblemHighlightType} enum value is specified instead of true/false
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageTarget;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.ApiStatus.Experimental;
|
||||
@@ -86,6 +87,7 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture {
|
||||
/**
|
||||
* @return the action context for current in-memory editor
|
||||
*/
|
||||
@RequiresReadLock
|
||||
default ModCommandAction.ActionContext getActionContext() {
|
||||
return ModCommandAction.ActionContext.from(getEditor(), getFile());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user