Merge pull request #35 from fernando88to/main
Go To Test: reach tests of sibling artefacts kept in another package
diff --git a/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GoToTestAction.java b/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GoToTestAction.java
index 39d4b45..98944f8 100644
--- a/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GoToTestAction.java
+++ b/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GoToTestAction.java
@@ -55,9 +55,11 @@
List<VirtualFile> result = new ArrayList<>();
for (GrailsArtifact artefactType : GrailsEditorToolbar.DECORATED_ARTEFACT_TYPES) {
- for (GrClassDefinition artifact : artefactType.getInstances(artefactData.getModule(),
- artefactData.getPackageName(),
- artefactData.getArtefactName())) {
+ // Same package-preferring lookup the Go To <artefact> actions use, so the tests of a sibling
+ // artefact kept in another package stay reachable.
+ for (GrClassDefinition artifact : artefactType.getInstancesPreferringPackage(artefactData.getModule(),
+ artefactData.getPackageName(),
+ artefactData.getArtefactName())) {
for (PsiClass testClass : GrailsTestUtils.getTestsForArtifact(artifact, true)) {
ContainerUtil.addIfNotNull(result, testClass.getContainingFile().getVirtualFile());
}
diff --git a/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GrailsGoToArtefactActionBase.java b/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GrailsGoToArtefactActionBase.java
index 8cfe12c..ca23441 100644
--- a/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GrailsGoToArtefactActionBase.java
+++ b/plugin/src/main/java/org/apache/grails/intellij/plugin/editor/toolbar/GrailsGoToArtefactActionBase.java
@@ -51,14 +51,9 @@
@Override
public @NotNull Collection<GrClassDefinition> getNavigateTargets(@NotNull ArtefactData artefactData) {
- // Prefer an artefact in the same package as the current one, but fall back to matching by name
- // alone: in multi-project builds a shared artefact (e.g. a domain in an upstream project) often
- // lives in a different package than the controller/service that uses it.
- Collection<GrClassDefinition> samePackage =
- myArtefactType.getInstances(artefactData.getModule(), artefactData.getPackageName(), artefactData.getArtefactName());
- return samePackage.isEmpty()
- ? myArtefactType.getInstances(artefactData.getModule(), artefactData.getArtefactName())
- : samePackage;
+ return myArtefactType.getInstancesPreferringPackage(artefactData.getModule(),
+ artefactData.getPackageName(),
+ artefactData.getArtefactName());
}
@Override
diff --git a/plugin/src/main/java/org/apache/grails/intellij/plugin/util/GrailsArtifact.java b/plugin/src/main/java/org/apache/grails/intellij/plugin/util/GrailsArtifact.java
index ce96cca..cb5ae3c 100644
--- a/plugin/src/main/java/org/apache/grails/intellij/plugin/util/GrailsArtifact.java
+++ b/plugin/src/main/java/org/apache/grails/intellij/plugin/util/GrailsArtifact.java
@@ -279,6 +279,23 @@
});
}
+ /**
+ * The artefacts named {@code artefactName} that the "Go To <artefact>" actions navigate to:
+ * the ones in {@code packageName} when there are any, otherwise every artefact with that name
+ * regardless of package.
+ *
+ * <p>Co-locating the artefacts of one concept is a convention, not a rule - a domain shared by an
+ * upstream project, or a controller kept under a {@code web} package, would otherwise be
+ * unreachable from the toolbar. Every caller must share this policy, so navigation from an
+ * artefact does not depend on which action is used.
+ */
+ public @NotNull Collection<GrClassDefinition> getInstancesPreferringPackage(@NotNull Module module,
+ @Nullable String packageName,
+ @NotNull String artefactName) {
+ Collection<GrClassDefinition> samePackage = getInstances(module, packageName, artefactName);
+ return samePackage.isEmpty() ? getInstances(module, artefactName) : samePackage;
+ }
+
private GrailsArtifactCache getCache(final @NotNull Module module) {
final Project project = module.getProject();
return CachedValuesManager.getManager(project).getCachedValue(module, cacheKey, () -> {
diff --git a/plugin/src/test/java/org/apache/grails/intellij/plugin/action/GoToTestActionTest.java b/plugin/src/test/java/org/apache/grails/intellij/plugin/action/GoToTestActionTest.java
new file mode 100644
index 0000000..2504686
--- /dev/null
+++ b/plugin/src/test/java/org/apache/grails/intellij/plugin/action/GoToTestActionTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.intellij.plugin.action;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import org.apache.grails.intellij.lib.testFramework.GrailsTestCase;
+import org.apache.grails.intellij.plugin.actions.ArtefactData;
+import org.apache.grails.intellij.plugin.editor.toolbar.GoToTestAction;
+import org.apache.grails.intellij.plugin.structure.GrailsApplication;
+import org.apache.grails.intellij.plugin.structure.GrailsApplicationManager;
+import org.apache.grails.intellij.plugin.util.GrailsArtifact;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * "Go To Test" collects the tests of every artefact that shares the current artefact's name, and used
+ * to require them to share its package too - so a domain kept under {@code model} was invisible from a
+ * controller under {@code web}, even though "Go To Domain" reaches it.
+ */
+public class GoToTestActionTest extends GrailsTestCase {
+
+ private void addProjectWithSplitPackages() {
+ addDomain("""
+
+package com.example.model
+
+class Book {
+ String title
+}
+""");
+ addController("""
+
+package com.example.web
+
+class BookController {
+ def index() {}
+}
+""");
+ myFixture.addFileToProject("test/unit/com/example/model/BookSpec.groovy", """
+
+package com.example.model
+
+class BookSpec {
+}
+""");
+ myFixture.addFileToProject("test/unit/com/example/web/BookControllerSpec.groovy", """
+
+package com.example.web
+
+class BookControllerSpec {
+}
+""");
+ }
+
+ private @NotNull ArtefactData artefactDataForController(@NotNull PsiFile controller) {
+ GrailsApplication application = GrailsApplicationManager.findApplication(controller);
+ assertNotNull("Grails application not found for " + controller.getName(), application);
+
+ VirtualFile file = controller.getVirtualFile();
+ assertNotNull(file);
+
+ return new ArtefactData(getProject(), getModule(), file, "com.example.web", "book", application, false);
+ }
+
+ private static @NotNull List<String> fileNames(@NotNull Collection<VirtualFile> files) {
+ List<String> names = new ArrayList<>();
+ for (VirtualFile file : files) names.add(file.getName());
+ return names;
+ }
+
+ /** The reported gap: the sibling artefact lives in another package, so its spec was dropped. */
+ public void testCollectsTestsOfSiblingArtefactInAnotherPackage() {
+ addProjectWithSplitPackages();
+ PsiFile controller = addController("""
+
+package com.example.web
+
+class OtherController {
+ def index() {}
+}
+""");
+
+ List<String> targets = fileNames(new GoToTestAction().getNavigateTargets(artefactDataForController(controller)));
+
+ assertTrue("the controller's own spec must be offered, got " + targets, targets.contains("BookControllerSpec.groovy"));
+ assertTrue("the domain's spec must be offered even from another package, got " + targets,
+ targets.contains("BookSpec.groovy"));
+ }
+
+ /** What made the tests unreachable: the strict lookup finds nothing outside the current package. */
+ public void testStrictLookupFindsNothingAcrossPackages() {
+ addProjectWithSplitPackages();
+
+ assertTrue("no domain named Book exists in com.example.web",
+ GrailsArtifact.DOMAIN.getInstances(getModule(), "com.example.web", "book").isEmpty());
+ assertFalse("the package-preferring lookup must reach com.example.model.Book",
+ GrailsArtifact.DOMAIN.getInstancesPreferringPackage(getModule(), "com.example.web", "book").isEmpty());
+ }
+
+ /** An artefact in the current package still wins, so co-located projects keep the narrower result. */
+ public void testSamePackageWins() {
+ addDomain("""
+
+package com.example.model
+
+class Book {
+ String title
+}
+""");
+ addDomain("""
+
+package com.example.web
+
+class Book {
+ String title
+}
+""");
+
+ Collection<?> targets = GrailsArtifact.DOMAIN.getInstancesPreferringPackage(getModule(), "com.example.web", "book");
+ assertEquals("only the same-package domain must be offered, got " + targets, 1, targets.size());
+ }
+}
\ No newline at end of file