package com.intellij.flex.codeInsight; import com.intellij.codeInsight.CodeInsightTestCase; import com.intellij.codeInsight.TargetElementUtil; import com.intellij.flex.util.FlexTestUtils; import com.intellij.javascript.flex.FlexDocumentationProvider; import com.intellij.javascript.flex.css.FlexStylesIndexableSetContributor; import com.intellij.lang.javascript.JSTestOption; import com.intellij.lang.javascript.JSTestOptions; import com.intellij.lang.javascript.flex.FlexModuleType; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.psi.stubs.JSQualifiedElementIndex; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.psi.*; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Consumer; import com.intellij.util.PathUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collection; import static com.intellij.openapi.vfs.VfsUtilCore.convertFromUrl; import static com.intellij.openapi.vfs.VfsUtilCore.urlToPath; @SuppressWarnings({"ConstantConditions"}) public class FlexNavigationTest extends CodeInsightTestCase { private static final String BASE_PATH = "/flex_navigation/"; protected Runnable myAfterCommitRunnable = null; @Override protected void setUp() throws Exception { VfsRootAccess.allowRootAccess(getTestRootDisposable(), urlToPath(convertFromUrl(FlexStylesIndexableSetContributor.class.getResource("FlexStyles.as")))); super.setUp(); myAfterCommitRunnable = null; } @Override protected void tearDown() throws Exception { myAfterCommitRunnable = null; super.tearDown(); } @Override protected void setUpJdk() { FlexTestUtils.setupFlexSdk(myModule, getTestName(false), getClass()); } protected ModuleType getModuleType() { return FlexModuleType.getInstance(); } @Override protected String getTestDataPath() { return FlexTestUtils.getTestDataPath(""); } @Override protected void doCommitModel(@NotNull ModifiableRootModel rootModel) { super.doCommitModel(rootModel); if (myAfterCommitRunnable != null) { myAfterCommitRunnable.run(); } } private void doTest(String filename, @Nullable VirtualFile expectedForSource, @Nullable VirtualFile expectedForDoc) throws Exception { doTest(filename, expectedForSource, expectedForDoc, null); } private static void checkClassName(@Nullable String expectedClassName, @NotNull PsiElement element) { if (expectedClassName == null) return; JSClass jsClass = PsiTreeUtil.getParentOfType(element, JSClass.class); assertNotNull("Parent class not found", jsClass); assertEquals(expectedClassName, jsClass.getName()); } private void doTest(String filename, @Nullable VirtualFile expectedForSource, @Nullable VirtualFile expectedForDoc, @Nullable String expectedClassName) throws Exception { configureByFile(BASE_PATH + filename); doTest(expectedForSource, expectedForDoc, expectedClassName); } private void doTest(VirtualFile expectedForSource, VirtualFile expectedForDoc, String expectedClassName) { doTest(myEditor, expectedForSource, expectedForDoc, expectedClassName, null); } public static void doTest(Editor editor, @Nullable VirtualFile expectedForSource, @Nullable VirtualFile expectedForDoc, @Nullable String expectedClassName, @Nullable Consumer<PsiElement> customCheck) { PsiElement clazz = TargetElementUtil .findTargetElement(editor, TargetElementUtil.ELEMENT_NAME_ACCEPTED | TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED); Collection<PsiElement> elementsToCheck; GlobalSearchScope scope; if (clazz == null || !(clazz instanceof JSQualifiedNamedElement)) { PsiReference reference = TargetElementUtil.findReference(editor); assertNotNull(reference); elementsToCheck = TargetElementUtil.getInstance().getTargetCandidates(reference); scope = null; } else { elementsToCheck = Arrays.asList(clazz); final PsiFile file = PsiDocumentManager.getInstance(editor.getProject()).getPsiFile(editor.getDocument()); final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset()); scope = JSResolveUtil.getResolveScope(elementAt); } assertTrue("Target elements not found", elementsToCheck.size() > 0); for (PsiElement element : elementsToCheck) { if (element instanceof JSQualifiedNamedElement) { String qName = ((JSQualifiedNamedElement)element).getQualifiedName(); GlobalSearchScope searchScope = scope != null ? scope : JSResolveUtil.getResolveScope(element); final Collection<JSQualifiedNamedElement> candidates = StubIndex.getElements(JSQualifiedElementIndex.KEY, qName.hashCode(), editor.getProject(), searchScope, JSQualifiedNamedElement.class); for (JSQualifiedNamedElement candidate : candidates) { if (!qName.equals(candidate.getQualifiedName())) { continue; } doCheck(candidate, expectedForSource, expectedForDoc, customCheck); checkClassName(expectedClassName, candidate); } } else { doCheck(element, expectedForSource, expectedForDoc, customCheck); checkClassName(expectedClassName, element); } } } private static void doCheck(PsiElement element, @Nullable VirtualFile expectedForSource, @Nullable VirtualFile expectedForDoc, @Nullable Consumer<PsiElement> customCheck) { if (customCheck != null) { customCheck.consume(element); } if (expectedForSource != null) { PsiElement source = element.getNavigationElement(); final String expected = PathUtil.getCanonicalPath(expectedForSource.getUrl()); final String actual = PathUtil.getCanonicalPath(source.getContainingFile().getVirtualFile().getUrl()); assertEquals("element with source", expected, actual); } if (expectedForDoc != null) { PsiElement doc = FlexDocumentationProvider.findTopLevelNavigationElement((JSQualifiedNamedElement)element); final String expected = PathUtil.getCanonicalPath(expectedForDoc.getUrl()); final String actual = PathUtil.getCanonicalPath(doc.getContainingFile().getVirtualFile().getUrl()); assertEquals("element with asdoc", expected, actual); } } private VirtualFile getFile(String relativePath) { return VirtualFileManager.getInstance() .findFileByUrl("jar://" + FileUtil.toSystemIndependentName(getTestDataPath()) + BASE_PATH + relativePath); } @JSTestOptions({JSTestOption.WithFlexSdk}) public void testLibraryClass1() throws Exception { final String sources = "TestLibSources.zip"; myAfterCommitRunnable = () -> { FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "TestLib1.swc", null, null); FlexTestUtils.addLibrary(myModule, "LibWithSources", getTestDataPath() + BASE_PATH, "TestLib2.swc", sources, null); FlexTestUtils.addLibrary(myModule, "LibWithAsdoc", getTestDataPath() + BASE_PATH, "TestLib3.swc", null, "TestLibAsdoc.zip"); }; final VirtualFile forSource = getFile(sources + "!/com/test/MyButton.as"); doTest("LibraryClass.as", forSource, forSource); } @JSTestOptions({JSTestOption.WithFlexSdk}) public void testLibraryClass2() throws Exception { final String libWithAsDocSwc = "TestLib3.swc"; myAfterCommitRunnable = () -> { FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "TestLib1.swc", null, null); FlexTestUtils.addLibrary(myModule, "LibWithAsdoc", getTestDataPath() + BASE_PATH, libWithAsDocSwc, null, "TestLibAsdoc.zip"); }; final VirtualFile forAsdoc = getFile(libWithAsDocSwc + "!/library.swf"); doTest("LibraryClass.as", null, forAsdoc); } @JSTestOptions({JSTestOption.WithFlexSdk}) public void testLibraryClass3() throws Exception { myAfterCommitRunnable = () -> FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "TestLib1.swc", null, null); final VirtualFile forSource = getFile("TestLib1.swc!/library.swf"); doTest("LibraryClass.as", forSource, forSource); } @JSTestOptions({JSTestOption.WithFlexSdk}) public void testSdkClass1() throws Exception { VirtualFile asdoc = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + "SdkAsdoc.zip"); asdoc = JarFileSystem.getInstance().getJarRootForLocalFile(asdoc); VirtualFile swc = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + "CustomSdk.swc"); swc = JarFileSystem.getInstance().getJarRootForLocalFile(swc); FlexTestUtils.setupCustomSdk(myModule, swc, null, asdoc); myAfterCommitRunnable = () -> FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "TestLib1.swc", null, null); VirtualFile forAsDoc = swc.findChild("library.swf"); doTest("SdkClass.as", null, forAsDoc); } @JSTestOptions({JSTestOption.WithFlexFacet}) public void testSdkClass2() throws Exception { VirtualFile asdoc = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + "SdkAsdoc.zip"); asdoc = JarFileSystem.getInstance().getJarRootForLocalFile(asdoc); VirtualFile swc = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + "CustomSdk.swc"); swc = JarFileSystem.getInstance().getJarRootForLocalFile(swc); FlexTestUtils.setupCustomSdk(myModule, swc, null, asdoc); myAfterCommitRunnable = () -> FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "TestLib1.swc", null, null); VirtualFile forAsDoc = swc.findChild("library.swf"); doTest("SdkClass.as", null, forAsDoc); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testAmbiguousCssSelector() throws Exception { final String sources = "StyleableLibSources.zip"; myAfterCommitRunnable = () -> { FlexTestUtils.addLibrary(myModule, "Lib1", getTestDataPath() + BASE_PATH, "StyleableLib.swc", sources, null); FlexTestUtils.addLibrary(myModule, "Lib2", getTestDataPath() + BASE_PATH, "StyleableLib1.swc", null, null); }; final VirtualFile styleableFile = getFile(sources + "!/foo/Styleable1.as"); doTest(getTestName(false) + ".css", styleableFile, null); PsiFile file = PsiManager.getInstance(myProject).findFile(styleableFile); if (file instanceof PsiFileImpl) { assertNull("File should not be parsed", ((PsiFileImpl)file).getTreeElement()); } } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testIncludes() throws Exception { VirtualFile vFile = configureByFiles(null, BASE_PATH + "Includes.css", BASE_PATH + "Includes.as", BASE_PATH + "Includes1.as"); VirtualFile file = vFile.getParent().findChild("Includes1.as"); doTest(file, file, null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssPropertyForCustomClass1() throws Exception { doCustomClassCssTest("!/foo/Styles.as", null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssPropertyForCustomClass2() throws Exception { doCustomClassCssTest("!/foo/Styles.as", "CssPropertyForCustomClass1"); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssPropertyForCustomClass3() throws Exception { doCustomClassCssTest("!/foo/Styleable2.as", null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssSelector() throws Exception { doLibClassCssTest(true, null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssSelector1() throws Exception { doLibClassCssTest(true, null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testLibCss() throws Exception { doTestLibCss(0, 1); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testLibCss1() throws Exception { doTestLibCss(1, 6); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testLibCss2() throws Exception { doTestLibCss(5, 6); } // we cannot use <caret> method, because lib css file is read-only private void doTestLibCss(int line, int column) throws Exception { myAfterCommitRunnable = () -> FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "LibWithCssFile.swc", "LibWithCssFile_src.zip", null); configureByFile(BASE_PATH + "CssEmptyFile.css"); final VirtualFile cssFile = getFile("LibWithCssFile.swc!/defaults.css"); configureByExistingFile(cssFile); myEditor.getCaretModel().moveToLogicalPosition(new LogicalPosition(line, column)); final VirtualFile classFile = getFile("LibWithCssFile_src.zip!/p1/p2/MyClass.as"); doTest(classFile, null, null); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssProperty1() throws Exception { doLibClassCssTest(false, "Container"); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssProperty2() throws Exception { doLibClassCssTest(false, "Button"); } @JSTestOptions({JSTestOption.WithCssSupportLoader, JSTestOption.WithFlexFacet}) public void testCssProperty3() throws Exception { doLibClassCssTest(false, null); } private void doCustomClassCssTest(@NotNull String expectedSourceClass, @Nullable String testName) throws Exception { if (testName == null) { testName = getTestName(false); } final String sources = "StyleableLibSources.zip"; myAfterCommitRunnable = () -> FlexTestUtils.addLibrary(myModule, "Lib", getTestDataPath() + BASE_PATH, "StyleableLib.swc", sources, null); final VirtualFile includedFile = getFile(sources + expectedSourceClass); doTest(testName + ".css", includedFile, null); PsiFile file = PsiManager.getInstance(myProject).findFile(includedFile); if (file instanceof PsiFileImpl) { assertNull("File should not be parsed", ((PsiFileImpl)file).getTreeElement()); } } private void doLibClassCssTest(boolean expectedForDoc, @Nullable String expectedClassName) throws Exception { String testName = getTestName(false); String mockFlex = FlexTestUtils.getPathToMockFlex(getClass(), testName) + "/MonkeyPatchingMockFlex.as"; VirtualFile file = LocalFileSystem.getInstance().findFileByPath(mockFlex); doTest(testName + ".css", file, expectedForDoc ? file : null, expectedClassName); } @JSTestOptions({JSTestOption.WithFlexSdk}) public void testMonkeyPatching() throws Exception { final String testName = getTestName(false); myAfterCommitRunnable = () -> { final VirtualFile sdkSrc = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + testName + "_sdk_src"); final SdkModificator sdkModificator = FlexTestUtils.getFlexSdkModificator(myModule); sdkModificator.addRoot(sdkSrc, OrderRootType.SOURCES); sdkModificator.commitChanges(); }; configureByFiles(BASE_PATH + testName, BASE_PATH + testName + "/" + testName + ".as", BASE_PATH + testName + "/mx/events/FlexEvent.as"); final VirtualFile expectedFile = LocalFileSystem.getInstance() .findFileByPath(ModuleRootManager.getInstance(myModule).getSourceRoots()[0].getPath() + "/mx/events/FlexEvent.as"); assert expectedFile != null; final PsiReference reference = TargetElementUtil.findReference(myEditor); assertNotNull(reference); final Collection<PsiElement> candidates = TargetElementUtil.getInstance().getTargetCandidates(reference); assertEquals(1, candidates.size()); doCheck(candidates.iterator().next(), expectedFile, expectedFile, null); } public void testClassWithNoExplicitConstructor() throws Exception { final String testName = getTestName(false); myAfterCommitRunnable = () -> FlexTestUtils .addFlexLibrary(false, myModule, "foo", true, getTestDataPath() + BASE_PATH, testName + ".swc", testName + ".zip", null); configureByFiles(null, BASE_PATH + testName + ".as"); final VirtualFile sourcesZip = LocalFileSystem.getInstance().findFileByPath(getTestDataPath() + BASE_PATH + testName + ".zip"); VirtualFile expectedForSource = JarFileSystem.getInstance().getJarRootForLocalFile(sourcesZip).findChild("MyClass3.as"); assertNotNull(expectedForSource); final PsiReference reference = TargetElementUtil.findReference(myEditor); assertNotNull(reference); final Collection<PsiElement> candidates = TargetElementUtil.getInstance().getTargetCandidates(reference); assertEquals(1, candidates.size()); doCheck(candidates.iterator().next(), expectedForSource, null, null); } }