Redesign test tasks choosing popup in accordance with doc in UX-487

This commit is contained in:
Sergei Vorobyov
2019-02-15 17:20:48 +03:00
committed by Sergei Vorobyov
parent 3aeb7ac39e
commit 61932662fe
7 changed files with 128 additions and 29 deletions
@@ -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
@@ -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<PsiElement, String> 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 {
@@ -89,7 +89,7 @@ public final class PatternGradleConfigurationProducer extends GradleTestRunConfi
Project project = context.getProject();
List<String> 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<String, PsiClass> findPsiClass = test -> testMappings.getClasses().get(test);
Function2<PsiClass, String, String> createFilter = (psiClass, test) ->
@@ -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<PsiClass, String> createFilter = (psiClass) -> createTestFilterFrom(psiClass, /*hasSuffix=*/true);
@@ -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<PsiClass, String> 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();
});
}
@@ -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<PsiElement>,
perform: Consumer<List<Map<SourcePath, TestTasks>>>
) {
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<List<Map<SourcePath, TestTasks>>>
) {
chooseTestTasks(context, elements.asIterable(), perform)
chooseTestTasks(project, context, elements.asIterable(), perform)
}
private fun chooseTestTasks(
project: Project,
context: DataContext,
sources: List<VirtualFile>,
project: Project,
perform: Consumer<List<Map<SourcePath, TestTasks>>>
) {
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<TestName, Map<SourcePath, TasksToRun>>,
perform: Consumer<List<Map<SourcePath, TestTasks>>>
) {
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<String?>("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 <K, V, R> Map<K, V>.mapNotNullValues(transform: (Map.Entry<K, V>) -> R?): Map<K, R> =
mapNotNull { entry -> transform(entry)?.let { entry.key to it } }.toMap()
}
@@ -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<TestName, Map<SourcePath, TasksToRun>>,
perform: Consumer<List<Map<SourcePath, TestTasks>>>) {
perform.accept(testTasks.filterKeys(testTasksFilter).values.toList())