diff --git a/python/src/com/jetbrains/python/codeInsight/PyLineMarkerNavigator.java b/python/src/com/jetbrains/python/codeInsight/PyLineMarkerNavigator.java index 500c49e8ddc1..8635876c2c6e 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyLineMarkerNavigator.java +++ b/python/src/com/jetbrains/python/codeInsight/PyLineMarkerNavigator.java @@ -18,6 +18,9 @@ package com.jetbrains.python.codeInsight; import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator; import com.intellij.ide.util.DefaultPsiElementCellRenderer; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolder; import com.intellij.psi.NavigatablePsiElement; import com.intellij.psi.PsiElement; import com.intellij.util.Processor; @@ -25,6 +28,7 @@ import com.intellij.util.Query; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -33,11 +37,17 @@ import java.util.List; /** * @author yole */ -public abstract class PyLineMarkerNavigator implements GutterIconNavigationHandler { +abstract class PyLineMarkerNavigator implements GutterIconNavigationHandler { + + private static final Key MARKERS = new Key("PyLineMarkerNavigatorMarkers"); + + @Override public void navigate(final MouseEvent e, final T elt) { final List navElements = new ArrayList(); final Query elementQuery = search(elt, TypeEvalContext.userInitiated(elt.getProject(), elt.getContainingFile())); - if (elementQuery == null) return; + if (elementQuery == null) { + return; + } elementQuery.forEach(new Processor() { public boolean process(final T psiElement) { if (psiElement instanceof NavigatablePsiElement) { @@ -46,8 +56,27 @@ public abstract class PyLineMarkerNavigator implements Gut return true; } }); + /** + * For test purposes, we should be able to access list of methods to check em. + * {@link PsiElementListNavigator} simply opens then (hence it is swing-based) and can't be used in tests. + * So, in unit tests we save data in element and data could be obtained with {@link #getNavigationTargets(UserDataHolder)} + */ final NavigatablePsiElement[] methods = navElements.toArray(new NavigatablePsiElement[navElements.size()]); - PsiElementListNavigator.openTargets(e, methods, getTitle(elt), null, new DefaultPsiElementCellRenderer()); + if (ApplicationManager.getApplication().isUnitTestMode()) { + elt.putUserData(MARKERS, methods); + } + else { + PsiElementListNavigator.openTargets(e, methods, getTitle(elt), null, new DefaultPsiElementCellRenderer()); + } + } + + /** + * @see {@link #navigate(MouseEvent, PsiElement)} and {@link #MARKERS} + */ + @TestOnly + @Nullable + static NavigatablePsiElement[] getNavigationTargets(@NotNull final UserDataHolder holder) { + return holder.getUserData(MARKERS); } protected abstract String getTitle(T elt); diff --git a/python/testData/lineMarkerTest/__init__.py b/python/testData/lineMarkerTest/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/lineMarkerTest/eggs/__init__.py b/python/testData/lineMarkerTest/eggs/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/lineMarkerTest/eggs/spam/__init__.py b/python/testData/lineMarkerTest/eggs/spam/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/lineMarkerTest/eggs/spam/eggs.py b/python/testData/lineMarkerTest/eggs/spam/eggs.py new file mode 100644 index 000000000000..b2ec85ea38d8 --- /dev/null +++ b/python/testData/lineMarkerTest/eggs/spam/eggs.py @@ -0,0 +1,3 @@ +class Eggs(object): + def ham(self): + pass \ No newline at end of file diff --git a/python/testData/lineMarkerTest/spam.py b/python/testData/lineMarkerTest/spam.py new file mode 100644 index 000000000000..ef4313c1c462 --- /dev/null +++ b/python/testData/lineMarkerTest/spam.py @@ -0,0 +1,4 @@ +import eggs.spam.eggs +class Child(eggs.spam.eggs.Eggs): + def ham(self): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/codeInsight/PyLineMarkerProviderTest.java b/python/testSrc/com/jetbrains/python/codeInsight/PyLineMarkerProviderTest.java new file mode 100644 index 000000000000..a555e268f994 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/codeInsight/PyLineMarkerProviderTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed 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 + * + * http://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 com.jetbrains.python.codeInsight; + +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.lang.ASTNode; +import com.intellij.psi.NavigatablePsiElement; +import com.intellij.psi.PsiElement; +import com.intellij.psi.tree.TokenSet; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyPossibleClassMember; +import org.hamcrest.Matchers; +import org.junit.Assert; + +import javax.swing.*; +import java.awt.event.MouseEvent; + + +/** + * @author Ilya.Kazakevich + */ +public final class PyLineMarkerProviderTest extends PyTestCase { + + /** + * Checks method has "up" arrow when overrides, and this arrow works + */ + public void testOverriding() throws Exception { + myFixture.copyDirectoryToProject("lineMarkerTest", ""); + myFixture.configureByFile("spam.py"); + + final ASTNode functionNode = myFixture.getElementAtCaret().getNode(); + // We need IDENTIFIER node + final ASTNode[] functionChildren = functionNode.getChildren(TokenSet.create(PyTokenTypes.IDENTIFIER)); + assert functionChildren.length == 1 : "Wrong number of identifiers: " + functionChildren.length; + final PsiElement element = functionChildren[0].getPsi(); + @SuppressWarnings("unchecked") + final LineMarkerInfo lineMarkerInfo = new PyLineMarkerProvider().getLineMarkerInfo(element); + Assert.assertNotNull("No gutter displayed", lineMarkerInfo); + final GutterIconNavigationHandler handler = lineMarkerInfo.getNavigationHandler(); + Assert.assertNotNull("Gutter has no navigation handle", handler); + handler.navigate(new MouseEvent(new JLabel(), 0, 0, 0, 0, 0, 0, false), element); + final NavigatablePsiElement[] targets = PyLineMarkerNavigator.getNavigationTargets(element); + Assert.assertNotNull("No navigation targets found", targets); + + Assert.assertThat("Wrong number of targets found", targets, Matchers.arrayWithSize(1)); + final NavigatablePsiElement parentMethod = targets[0]; + Assert.assertThat("Navigation target has wrong type", parentMethod, Matchers.instanceOf(PyPossibleClassMember.class)); + final PyClass parentClass = ((PyPossibleClassMember)parentMethod).getContainingClass(); + Assert.assertNotNull("Function overrides other function, but no parent displayed", parentClass); + Assert.assertEquals("Wrong parent class name", "Eggs", parentClass.getName()); + } +} \ No newline at end of file