package com.intellij.flex.uml; import com.intellij.codeInsight.CodeInsightTestCase; import com.intellij.codeInsight.TargetElementUtil; import com.intellij.diagram.DiagramBuilder; import com.intellij.diagram.DiagramDataModel; import com.intellij.diagram.DiagramNode; import com.intellij.diagram.DiagramProvider; import com.intellij.diagram.settings.DiagramConfiguration; import com.intellij.diagram.util.DiagramUtils; import com.intellij.diagram.util.UmlDataModelDumper; import com.intellij.flex.util.FlexTestUtils; import com.intellij.ide.DataManager; import com.intellij.javascript.flex.css.FlexStylesIndexableSetContributor; import com.intellij.javascript.flex.mxml.schema.FlexSchemaHandler; import com.intellij.lang.javascript.JSTestOption; import com.intellij.lang.javascript.JSTestOptions; import com.intellij.lang.javascript.JSTestUtils; import com.intellij.lang.javascript.flex.FlexModuleType; import com.intellij.lang.javascript.flex.projectStructure.model.ModifiableFlexBuildConfiguration; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.uml.FlashUmlDataModel; import com.intellij.lang.javascript.uml.FlashUmlDependenciesSettingsOption; import com.intellij.lang.javascript.uml.FlashUmlProvider; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.SkipInHeadlessEnvironment; import com.intellij.uml.UmlGraphBuilderFactory; import com.intellij.util.ArrayUtil; import com.intellij.util.JDOMCompare; import com.intellij.util.JdomKt; import com.intellij.util.containers.ContainerUtil; import org.jdom.Element; import org.jdom.transform.JDOMResult; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.*; import static com.intellij.openapi.vfs.VfsUtilCore.convertFromUrl; import static com.intellij.openapi.vfs.VfsUtilCore.urlToPath; @SkipInHeadlessEnvironment public class FlashUmlTest extends CodeInsightTestCase { private static final String BASE_PATH = "uml/"; @Override protected void setUp() throws Exception { VfsRootAccess.allowRootAccess(getTestRootDisposable(), urlToPath(convertFromUrl(FlexSchemaHandler.class.getResource("z.xsd"))), urlToPath(convertFromUrl(FlexStylesIndexableSetContributor.class.getResource("FlexStyles.as")))); super.setUp(); } @Override protected String getTestDataPath() { return FlexTestUtils.getTestDataPath(""); } @Override protected ModuleType getModuleType() { return FlexModuleType.getInstance(); } @Override protected void setUpJdk() { FlexTestUtils.setupFlexSdk(getModule(), getTestName(false), getClass()); } @Override public Object getData(String dataId) { if (dataId.equals(CommonDataKeys.PSI_ELEMENT.getName()) || dataId.equals(AnActionEvent.injectedId(CommonDataKeys.PSI_ELEMENT.getName()))) { return TargetElementUtil.findTargetElement(getEditor(), TargetElementUtil.getInstance().getReferenceSearchFlags()); } if (dataId.equals(CommonDataKeys.PSI_FILE.getName())) { return getFile(); } return super.getData(dataId); } private void doTest(String file) throws Exception { doTest(new String[]{file}, ArrayUtil.EMPTY_STRING_ARRAY, () -> GlobalSearchScope.allScope(myProject), null, null); } private void doTest(String[] files, String[] additionalClasses, Computable<GlobalSearchScope> scopeProvider, @Nullable EnumSet<FlashUmlDependenciesSettingsOption> dependencies, @Nullable String expectedFileNamePrefix) throws Exception { doTestImpl(null, files, additionalClasses, scopeProvider, dependencies, expectedFileNamePrefix); } private DiagramBuilder doTestImpl(@Nullable File projectRoot, String[] files, String[] additionalClasses, Computable<GlobalSearchScope> scopeProvider, @Nullable EnumSet<FlashUmlDependenciesSettingsOption> dependencies, @Nullable String expectedFileNamePrefix) throws Exception { List<VirtualFile> vFiles = new ArrayList<>(files.length); for (String file : files) { vFiles.add(getVirtualFile(BASE_PATH + file)); } ApplicationManager.getApplication().runWriteAction(() -> { final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule); final ModifiableRootModel rootModel = rootManager.getModifiableModel(); ContentEntry[] contentEntries = rootModel.getContentEntries(); for (ContentEntry contentEntry : contentEntries) { rootModel.removeContentEntry(contentEntry); } rootModel.commit(); }); configureByFiles(projectRoot, VfsUtilCore.toVirtualFileArray(vFiles)); final LinkedHashMap<Integer, String> markers = JSTestUtils.extractPositionMarkers(getProject(), getEditor().getDocument()); assertFalse(markers.isEmpty()); DiagramBuilder builder = null; int i = 1; for (Map.Entry<Integer, String> marker : markers.entrySet()) { getEditor().getCaretModel().moveToOffset(marker.getKey()); i++; String expectedPrefix = StringUtil.isNotEmpty(marker.getValue()) ? marker.getValue() : expectedFileNamePrefix; final DataContext dataContext = DataManager.getInstance().getDataContext(); final DiagramProvider[] providers = DiagramProvider.findProviders(dataContext, "unknown"); final FlashUmlProvider provider = ContainerUtil.findInstance(providers, FlashUmlProvider.class); assertNotNull("Flash UML provider not found", provider); final String actualOriginFqn = provider.getVfsResolver().getQualifiedName(provider.getElementManager().findInDataContext(dataContext)); final Object actualOrigin = provider.getVfsResolver().resolveElementByFQN(actualOriginFqn, getProject()); builder = UmlGraphBuilderFactory.create(myProject, provider, actualOrigin, null); Disposer.register(getTestRootDisposable(), builder); final DiagramDataModel<Object> model = builder.getDataModel(); DiagramConfiguration configuration = DiagramConfiguration.getConfiguration(); String originalCategories = configuration.categories.get(provider.getID()); if (dependencies != null) { model.setShowDependencies(true); EnumSet<FlashUmlDependenciesSettingsOption> disabledOptions = EnumSet.complementOf(dependencies); configuration.categories .put(provider.getID(), StringUtil.join(disabledOptions, option -> option.getDisplayName(), ";")); } else { model.setShowDependencies(false); } try { model.refreshDataModel(); // first limit elements by scope Collection<DiagramNode<Object>> nodesToRemove = new ArrayList<>(); for (DiagramNode<Object> node : model.getNodes()) { if (node.getIdentifyingElement() instanceof JSClass && !scopeProvider.compute().contains(((JSClass)node.getIdentifyingElement()).getContainingFile().getVirtualFile())) { nodesToRemove.add(node); } } for (DiagramNode<Object> node : nodesToRemove) { model.removeNode(node); } builder.updateGraph(); // then add explicitly required classes for (String aClass : additionalClasses) { JSClass c = JSTestUtils.findClassByQName(aClass, GlobalSearchScope.allScope(myProject)); final DiagramNode node = model.addElement(c); if (node != null) { builder.createDraggedNode(node, node.getTooltip(), DiagramUtils.getBestPositionForNode(builder)); builder.updateGraph(); } } assertModel(expectedPrefix, provider, actualOriginFqn, model); } finally { configuration.categories.put(provider.getID(), originalCategories); } } assert builder != null; return builder; } private void assertModel(final String expectedPrefix, final DiagramProvider<Object> provider, final String actualOriginFqn, final DiagramDataModel<Object> model) throws Exception { String expectedDataFileName = getTestName(false) + (StringUtil.isEmpty(expectedPrefix) ? ".expected.xml" : ".expected." + expectedPrefix + ".xml"); CharSequence expectedText = LoadTextUtil.loadText(getVirtualFile(BASE_PATH + expectedDataFileName)); final Element expected = JdomKt.loadElement(expectedText); final String expectedOriginFqn = expected.getAttributeValue("origin"); assertEquals(expectedDataFileName + ": Invalid origin element", expectedOriginFqn, actualOriginFqn); JDOMResult actual = new JDOMResult(); UmlDataModelDumper.dump(actual, provider, model); actual.getDocument().getRootElement().setAttribute("origin", actualOriginFqn); String difference = JDOMCompare.diffElements(expected, actual.getDocument().getRootElement()); if (difference != null) { // this will fail if structure is different assertEquals(expectedDataFileName + ": " + difference, JDOMUtil.writeElement(expected), JDOMUtil.writeElement(actual.getDocument().getRootElement()).trim()); } } public void testClasses() throws Exception { doTest(getTestName(false) + ".as"); } @JSTestOptions({JSTestOption.WithFlexFacet}) public void testMxmlClass() throws Exception { doTest(getTestName(false) + ".mxml"); } public void testPackage() throws Exception { doTest(getTestName(false) + ".as"); } public void testAsDependencies() throws Exception { String testName = getTestName(false); String filename = testName + ".as"; doTest(new String[]{filename, testName + "_2.as", testName + "_3.as"}, new String[]{"Foo", "Bar", "Zz", "Zz2", "Pp", "Oo", "Abc", "Def", "Rt", "UI", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "W", filename + ":Inner1", filename + ":Inner2"}, allScopeProvider(), EnumSet.allOf(FlashUmlDependenciesSettingsOption.class), null); } private Computable<GlobalSearchScope> projectScopeProvider() { return () -> GlobalSearchScope.projectScope(myProject); } private Computable<GlobalSearchScope> allScopeProvider() { return () -> GlobalSearchScope.allScope(myProject); } private Computable<GlobalSearchScope> moduleScopeProvider() { return () -> GlobalSearchScope.moduleScope(myModule); } public void testMxmlDependencies() throws Exception { initSdk(); String testName = getTestName(false); doTest(new String[]{testName + ".mxml", testName + "_2.as", testName + "_3.mxml", testName + "_4.mxml"}, new String[]{"Foo", "Bar", "Hello", "spark.components.Button", testName + "_3", testName + "_4", "com.foo.MyRenderer", "MySkin", "com.bar.A"}, projectScopeProvider(), EnumSet.allOf(FlashUmlDependenciesSettingsOption.class), null); } private void initSdk() { final Sdk sdk45 = FlexTestUtils.createSdk(FlexTestUtils.getPathToCompleteFlexSdk("4.5"), null, true); FlexTestUtils.modifyConfigs(myProject, editor -> { ModifiableFlexBuildConfiguration bc1 = editor.getConfigurations(myModule)[0]; FlexTestUtils.setSdk(bc1, sdk45); }); } public void testDependenciesSettings() throws Exception { String testName = getTestName(false); String filename = testName + ".as"; String[] files = {testName + ".as"}; String[] classes = {filename + ":C1", filename + ":C2", filename + ":C3", filename + ":C4", filename + ":C5"}; doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.ONE_TO_ONE), "OneToOne"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.ONE_TO_MANY), "OneToMany"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.CREATE), "Create"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.USAGES), "Usages"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.SELF), "Self"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.SELF, FlashUmlDependenciesSettingsOption.ONE_TO_ONE), "SelfOneToOne"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.SELF, FlashUmlDependenciesSettingsOption.ONE_TO_MANY), "SelfOneToMany"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.SELF, FlashUmlDependenciesSettingsOption.USAGES), "SelfUsages"); doTest(files, classes, projectScopeProvider(), EnumSet.of(FlashUmlDependenciesSettingsOption.SELF, FlashUmlDependenciesSettingsOption.CREATE), "SelfCreate"); } public void testVector() throws Exception { initSdk(); String fileName = getTestName(false) + ".as"; doTest(new String[]{fileName}, new String[]{"Vector", fileName + ":Foo", fileName + ":Bar"}, allScopeProvider(), EnumSet.allOf(FlashUmlDependenciesSettingsOption.class), null); } public void testExpandCollapse() throws Exception { File projectRoot = new File(getVirtualFile(BASE_PATH + getTestName(false)).getPath()); String[] files = {getTestName(false) + "/Classes.as", getTestName(false) + "/com/test/MyButton.mxml", getTestName(false) + "/com/test/MyButton2.mxml"}; DiagramBuilder builder = doTestImpl(projectRoot, files, new String[]{"com.test.Bar", "Root", "com.test.MyButton"}, moduleScopeProvider(), EnumSet.allOf(FlashUmlDependenciesSettingsOption.class), null); String originQName = "com.test.Foo"; DiagramProvider<Object> provider = DiagramProvider.findByID(FlashUmlProvider.ID); FlashUmlDataModel model = (FlashUmlDataModel)builder.getDataModel(); collapseNode(model, JSTestUtils.findClassByQName("com.test.Bar", myModule.getModuleScope())); assertModel("2", provider, originQName, model); expandNode(model, "com.test"); assertModel("3", provider, originQName, model); collapseNode(model, JSTestUtils.findClassByQName("Root", myModule.getModuleScope())); assertModel("3", provider, originQName, model); } public void testExpandCollapse2() throws Exception { File projectRoot = new File(getVirtualFile(BASE_PATH + getTestName(false)).getPath()); String[] files = {getTestName(false) + "/com/test/MyButton.mxml"}; DiagramBuilder builder = doTestImpl(projectRoot, files, ArrayUtil.EMPTY_STRING_ARRAY, moduleScopeProvider(), EnumSet.allOf(FlashUmlDependenciesSettingsOption.class), null); String originQName = "com.test.MyButton"; DiagramProvider<Object> provider = DiagramProvider.findByID(FlashUmlProvider.ID); FlashUmlDataModel model = (FlashUmlDataModel)builder.getDataModel(); collapseNode(model, JSTestUtils.findClassByQName("com.test.MyButton", myModule.getModuleScope())); assertModel("2", provider, originQName, model); } private static void collapseNode(final FlashUmlDataModel model, final Object element) { model.collapseNode(model.findNode(element)); model.refreshDataModel(); } private static void expandNode(final FlashUmlDataModel model, final Object element) { model.expandNode(model.findNode(element)); model.refreshDataModel(); } }