From 61932662feb4cf8c84a2cc4ef8b2263cba5c5914 Mon Sep 17 00:00:00 2001 From: Sergei Vorobyov Date: Thu, 14 Feb 2019 16:31:01 +0300 Subject: [PATCH] Redesign test tasks choosing popup in accordance with doc in UX-487 --- .../src/messages/ExecutionBundle.properties | 5 + ...lInPackageGradleConfigurationProducer.java | 16 ++- .../PatternGradleConfigurationProducer.java | 2 +- .../TestClassGradleConfigurationProducer.java | 5 +- ...TestMethodGradleConfigurationProducer.java | 7 +- .../execution/test/runner/TestTasksChooser.kt | 118 +++++++++++++++--- ...dleTestRunConfigurationProducerTestCase.kt | 4 +- 7 files changed, 128 insertions(+), 29 deletions(-) diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index 2635cc84bde1..dc3d3b11a1fe 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -424,3 +424,8 @@ run.dashboard.group.configurations.label=Group Name: run.dashboard.configurable.add.configuration.type=Add Configuration Type run.dashboard.configurable.types.panel.title=Configurations available in Run Dashboard application.configuration.include.provided.scope=Include dependencies with "Provided" scope + +tests.tasks.choosing.popup.title.common=Run Tasks +tests.tasks.choosing.popup.title=Run Tasks for {0} +tests.tasks.choosing.popup.hint=Select several tasks to run them at once +tests.tasks.choosing.warning.text=No test tasks available diff --git a/plugins/gradle/java/src/execution/test/runner/AllInPackageGradleConfigurationProducer.java b/plugins/gradle/java/src/execution/test/runner/AllInPackageGradleConfigurationProducer.java index 5e44b10f5091..481b6a69bfb3 100644 --- a/plugins/gradle/java/src/execution/test/runner/AllInPackageGradleConfigurationProducer.java +++ b/plugins/gradle/java/src/execution/test/runner/AllInPackageGradleConfigurationProducer.java @@ -6,6 +6,7 @@ import com.intellij.execution.actions.ConfigurationContext; import com.intellij.execution.actions.ConfigurationFromContext; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.junit.JavaRuntimeConfigurationProducerBase; +import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -16,7 +17,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.ArrayUtil; -import com.intellij.util.containers.ContainerUtil; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,11 +86,13 @@ public final class AllInPackageGradleConfigurationProducer extends GradleTestRun performRunnable.run(); return; } - getTestTasksChooser().chooseTestTasks(context, ContainerUtil.newArrayList(configurationData.sourceElement), tasks -> { + String locationName = String.format("'%s'", extractLocationName(configurationData.psiPackage, configurationData.module)); + DataContext dataContext = TestTasksChooser.contextWithLocationName(context.getDataContext(), locationName); + PsiElement[] sourceElements = ArrayUtil.toObjectArray(PsiElement.class, configurationData.sourceElement); + getTestTasksChooser().chooseTestTasks(context.getProject(), dataContext, sourceElements, tasks -> { ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); ExternalSystemTaskExecutionSettings settings = configuration.getSettings(); Function1 createFilter = (e) -> createTestFilterFrom(configurationData.psiPackage, /*hasSuffix=*/false); - PsiElement[] sourceElements = ArrayUtil.toObjectArray(PsiElement.class, configurationData.sourceElement); if (!applyTestConfiguration(settings, context.getModule(), tasks, sourceElements, createFilter)) { LOG.warn("Cannot apply package test configuration, uses raw run configuration"); performRunnable.run(); @@ -140,10 +142,12 @@ public final class AllInPackageGradleConfigurationProducer extends GradleTestRun return sourceDirs[0]; } + private static String extractLocationName(@NotNull PsiPackage aPackage, @NotNull Module module) { + return aPackage.getQualifiedName().isEmpty() ? module.getName() : aPackage.getQualifiedName(); + } + private static String suggestName(@NotNull PsiPackage aPackage, @NotNull Module module) { - return aPackage.getQualifiedName().isEmpty() - ? ExecutionBundle.message("test.in.scope.presentable.text", module.getName()) - : ExecutionBundle.message("test.in.scope.presentable.text", aPackage.getQualifiedName()); + return ExecutionBundle.message("test.in.scope.presentable.text", extractLocationName(aPackage, module)); } private static class ConfigurationData { diff --git a/plugins/gradle/java/src/execution/test/runner/PatternGradleConfigurationProducer.java b/plugins/gradle/java/src/execution/test/runner/PatternGradleConfigurationProducer.java index 5b155cb6147b..f8dd8f10763a 100644 --- a/plugins/gradle/java/src/execution/test/runner/PatternGradleConfigurationProducer.java +++ b/plugins/gradle/java/src/execution/test/runner/PatternGradleConfigurationProducer.java @@ -89,7 +89,7 @@ public final class PatternGradleConfigurationProducer extends GradleTestRunConfi Project project = context.getProject(); List tests = getTestPatterns(context); TestMappings testMappings = getTestMappings(project, tests); - getTestTasksChooser().chooseTestTasks(context, testMappings.getClasses().values(), tasks -> { + getTestTasksChooser().chooseTestTasks(project, context.getDataContext(), testMappings.getClasses().values(), tasks -> { ExternalSystemTaskExecutionSettings settings = configuration.getSettings(); Function1 findPsiClass = test -> testMappings.getClasses().get(test); Function2 createFilter = (psiClass, test) -> diff --git a/plugins/gradle/java/src/execution/test/runner/TestClassGradleConfigurationProducer.java b/plugins/gradle/java/src/execution/test/runner/TestClassGradleConfigurationProducer.java index 7d3e671f8773..6b3ccf6b6077 100644 --- a/plugins/gradle/java/src/execution/test/runner/TestClassGradleConfigurationProducer.java +++ b/plugins/gradle/java/src/execution/test/runner/TestClassGradleConfigurationProducer.java @@ -10,6 +10,7 @@ import com.intellij.execution.actions.ConfigurationFromContext; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.junit.InheritorChooser; +import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -162,7 +163,9 @@ public class TestClassGradleConfigurationProducer extends GradleTestRunConfigura @NotNull ConfigurationContext context, @NotNull Runnable performRunnable, @NotNull PsiClass... classes) { - getTestTasksChooser().chooseTestTasks(context, classes, tasks -> { + String locationName = classes.length == 1 ? classes[0].getName() : null; + DataContext dataContext = TestTasksChooser.contextWithLocationName(context.getDataContext(), locationName); + getTestTasksChooser().chooseTestTasks(context.getProject(), dataContext, classes, tasks -> { ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); ExternalSystemTaskExecutionSettings settings = configuration.getSettings(); Function1 createFilter = (psiClass) -> createTestFilterFrom(psiClass, /*hasSuffix=*/true); diff --git a/plugins/gradle/java/src/execution/test/runner/TestMethodGradleConfigurationProducer.java b/plugins/gradle/java/src/execution/test/runner/TestMethodGradleConfigurationProducer.java index 47d0ab01c59e..e7b0b642b10f 100644 --- a/plugins/gradle/java/src/execution/test/runner/TestMethodGradleConfigurationProducer.java +++ b/plugins/gradle/java/src/execution/test/runner/TestMethodGradleConfigurationProducer.java @@ -8,6 +8,7 @@ import com.intellij.execution.actions.ConfigurationFromContext; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.junit.InheritorChooser; +import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -131,7 +132,9 @@ public class TestMethodGradleConfigurationProducer extends GradleTestRunConfigur @NotNull Runnable performRunnable, @NotNull PsiMethod psiMethod, @NotNull PsiClass... classes) { - getTestTasksChooser().chooseTestTasks(context, classes, tasks -> { + String locationName = (classes.length == 1 ? classes[0].getName() + "." : "") + psiMethod.getName(); + DataContext dataContext = TestTasksChooser.contextWithLocationName(context.getDataContext(), locationName); + getTestTasksChooser().chooseTestTasks(context.getProject(), dataContext, classes, tasks -> { ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); ExternalSystemTaskExecutionSettings settings = configuration.getSettings(); Function1 createFilter = (psiClass) -> createTestFilter(context.getLocation(), psiClass, psiMethod); @@ -140,7 +143,7 @@ public class TestMethodGradleConfigurationProducer extends GradleTestRunConfigur performRunnable.run(); return; } - configuration.setName((classes.length == 1 ? classes[0].getName() + "." : "") + psiMethod.getName()); + configuration.setName(locationName); performRunnable.run(); }); } diff --git a/plugins/gradle/java/src/execution/test/runner/TestTasksChooser.kt b/plugins/gradle/java/src/execution/test/runner/TestTasksChooser.kt index 023a3852d40e..7025aa53a226 100644 --- a/plugins/gradle/java/src/execution/test/runner/TestTasksChooser.kt +++ b/plugins/gradle/java/src/execution/test/runner/TestTasksChooser.kt @@ -1,18 +1,34 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.gradle.execution.test.runner -import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.ExecutionBundle +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.DataKey +import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.ex.EditorGutterComponentEx import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement +import com.intellij.psi.search.scope.TestsScope +import com.intellij.ui.FileColorManager +import com.intellij.ui.awt.RelativePoint +import com.intellij.util.FunctionUtil +import com.intellij.util.ui.JBUI +import icons.ExternalSystemIcons import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestRunConfigurationProducer.findAllTestsTaskToRun import org.jetbrains.plugins.gradle.util.TasksToRun +import java.awt.Component +import java.awt.MouseInfo import java.util.function.Consumer import javax.swing.DefaultListCellRenderer +import javax.swing.JList +import javax.swing.border.EmptyBorder typealias SourcePath = String typealias TestName = String @@ -25,33 +41,35 @@ open class TestTasksChooser { private fun error(message: String): Nothing = LOG.error(message) as Nothing fun chooseTestTasks( - context: ConfigurationContext, + project: Project, + context: DataContext, elements: Iterable, perform: Consumer>> ) { val sources = elements.map { getSourceFile(it) ?: error("Can not find source file for $it") } - chooseTestTasks(context.dataContext, sources, context.project, perform) + chooseTestTasks(project, context, sources, perform) } fun chooseTestTasks( - context: ConfigurationContext, + project: Project, + context: DataContext, vararg elements: PsiElement, perform: Consumer>> ) { - chooseTestTasks(context, elements.asIterable(), perform) + chooseTestTasks(project, context, elements.asIterable(), perform) } private fun chooseTestTasks( + project: Project, context: DataContext, sources: List, - project: Project, perform: Consumer>> ) { val testTasks = findAllTestsTaskToRun(sources, project) when { - testTasks.isEmpty() -> showWarningTooltip(context) + testTasks.isEmpty() -> showTestsNotFoundWarning(project, context) testTasks.size == 1 -> perform.accept(testTasks.values.toList()) - else -> chooseTestTasks(context, testTasks, perform) + else -> chooseTestTasks(project, context, testTasks, perform) } } @@ -66,37 +84,101 @@ open class TestTasksChooser { } protected open fun chooseTestTasks( + project: Project, context: DataContext, testTasks: Map>, perform: Consumer>> ) { assert(!ApplicationManager.getApplication().isCommandLine) + val sortedTestTasksNames = testTasks.keys.toList().sortedByDescending { it == TEST_TASK_NAME } + val testTaskRenderer = TestTaskListCellRenderer(project) JBPopupFactory.getInstance() - .createPopupChooserBuilder(testTasks.keys.toList()) - .setRenderer(DefaultListCellRenderer()) - .setTitle("Choose Tasks to Run") - .setMovable(false) + .createPopupChooserBuilder(sortedTestTasksNames) + .setRenderer(testTaskRenderer) + .setTitle(suggestPopupTitle(context)) + .setAutoselectOnMouseMove(false) + .setNamerForFiltering(FunctionUtil.id()) + .setMovable(true) + .setAdText(ExecutionBundle.message("tests.tasks.choosing.popup.hint")) .setResizable(false) .setRequestFocus(true) + .setMinSize(JBUI.size(250, -1)) .setItemsChosenCallback { val choosesTestTasks = it.mapNotNull(testTasks::get) when { - choosesTestTasks.isEmpty() -> showWarningTooltip(context) + choosesTestTasks.isEmpty() -> showTestsNotFoundWarning(project, context) else -> perform.accept(choosesTestTasks) } } .createPopup() - .showInBestPositionFor(context) + .show(getPreferredPopupPosition(context)) } - protected open fun showWarningTooltip(context: DataContext) { + protected open fun showTestsNotFoundWarning(project: Project, context: DataContext) { assert(!ApplicationManager.getApplication().isCommandLine) - JBPopupFactory.getInstance() - .createMessage("No tasks available") - .showInBestPositionFor(context) + val notification = Notification( + "Test tasks chooser", + ExecutionBundle.message("tests.tasks.choosing.warning.text"), + "", + NotificationType.WARNING) + Notifications.Bus.notify(notification, project) + } + + private fun suggestPopupTitle(context: DataContext): String { + val locationName = context.getData(LOCATION) + return when (locationName) { + null -> ExecutionBundle.message("tests.tasks.choosing.popup.title.common") + else -> ExecutionBundle.message("tests.tasks.choosing.popup.title", locationName) + } + } + + private fun getPreferredPopupPosition(context: DataContext): RelativePoint { + return when (context.getData(PlatformDataKeys.CONTEXT_COMPONENT)) { + is EditorGutterComponentEx -> RelativePoint(MouseInfo.getPointerInfo().location) + else -> JBPopupFactory.getInstance().guessBestPopupLocation(context) + } + } + + private class TestTaskListCellRenderer(project: Project) : DefaultListCellRenderer() { + private val cellInsets = JBUI.insets(1, 5) + private val colorManager = FileColorManager.getInstance(project) + + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) + text = value.toString() + icon = ExternalSystemIcons.Task + iconTextGap = cellInsets.left + border = EmptyBorder(cellInsets) + if (!isSelected) { + background = colorManager.getScopeColor(TestsScope.NAME) + } + return this + } } companion object { + private const val TEST_TASK_NAME = "test" + + @JvmField + val LOCATION = DataKey.create("org.jetbrains.plugins.gradle.execution.test.runner.TestTasksChooser.LOCATION") + + @JvmStatic + fun contextWithLocationName(context: DataContext, locationName: String?): DataContext { + if (locationName == null) return context + return DataContext { + when { + LOCATION.`is`(it) -> locationName + else -> context.getData(it) + } + } + } + private fun Map.mapNotNullValues(transform: (Map.Entry) -> R?): Map = mapNotNull { entry -> transform(entry)?.let { entry.key to it } }.toMap() } diff --git a/plugins/gradle/java/testSources/execution/test/runner/GradleTestRunConfigurationProducerTestCase.kt b/plugins/gradle/java/testSources/execution/test/runner/GradleTestRunConfigurationProducerTestCase.kt index 439ab2347353..6b623b133732 100644 --- a/plugins/gradle/java/testSources/execution/test/runner/GradleTestRunConfigurationProducerTestCase.kt +++ b/plugins/gradle/java/testSources/execution/test/runner/GradleTestRunConfigurationProducerTestCase.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.LangDataKeys import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.Project import com.intellij.openapi.util.Ref import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiClass @@ -70,7 +71,8 @@ abstract class GradleTestRunConfigurationProducerTestCase : GradleImportingTestC protected fun GradleTestRunConfigurationProducer.setTestTasksChooser(testTasksFilter: (TestName) -> Boolean) { testTasksChooser = object : TestTasksChooser() { - override fun chooseTestTasks(context: DataContext, + override fun chooseTestTasks(project: Project, + context: DataContext, testTasks: Map>, perform: Consumer>>) { perform.accept(testTasks.filterKeys(testTasksFilter).values.toList())