From e8b1cfd99ab02a62b2761c03e3ad7e9d2def29a1 Mon Sep 17 00:00:00 2001 From: Anton Kozub Date: Thu, 14 Nov 2024 15:28:57 +0100 Subject: [PATCH] IDEA-361348 Spring Structure: inconsistent representation of @Bean methods (cherry picked from commit 543f5219ac51fea0a6d1c0a50745a34460bc125f) IJ-CR-151083 GitOrigin-RevId: 8f750b5983445b9d0e5456d179a21d51855cd967 --- .../LogicalStructureTestUtils.kt | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 java/testFramework/src/com/intellij/testFramework/LogicalStructureTestUtils.kt diff --git a/java/testFramework/src/com/intellij/testFramework/LogicalStructureTestUtils.kt b/java/testFramework/src/com/intellij/testFramework/LogicalStructureTestUtils.kt new file mode 100644 index 000000000000..030f417d5cd6 --- /dev/null +++ b/java/testFramework/src/com/intellij/testFramework/LogicalStructureTestUtils.kt @@ -0,0 +1,112 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.testFramework + +import com.intellij.ide.projectView.PresentationData +import com.intellij.ide.structureView.StructureViewTreeElement +import com.intellij.ide.structureView.logical.impl.LogicalStructureViewService +import com.intellij.ide.util.treeView.PresentableNodeDescriptor +import com.intellij.psi.PsiFile +import com.intellij.ui.SimpleTextAttributes +import junit.framework.AssertionFailedError +import junit.framework.ComparisonFailure +import javax.swing.Icon + +object LogicalStructureTestUtils { + + private const val MAX_DEPTH = 20 + + fun assertLogicalStructureForFile( + psiFile: PsiFile, + expectedStructureInitializer: LogicalStructureNode.() -> Unit, + ) { + val project = psiFile.project + val builder = LogicalStructureViewService.getInstance(project).getLogicalStructureBuilder(psiFile) + val structureView = builder!!.createStructureView(null, project) + val actualRoot = createActualNode(structureView.treeModel.root) + val expectedRoot = LogicalStructureNode(null, "root", "") + expectedRoot.expectedStructureInitializer() + if (!expectedRoot.isEqualTo(actualRoot, false)) { + throw ComparisonFailure("The models are not equals: ", + expectedRoot.print("", false), + actualRoot.print("", false)) + } + } + + private fun createActualNode(element: StructureViewTreeElement): LogicalStructureNode { + val presentation = element.presentation + val node = LogicalStructureNode( + presentation.getIcon(false), + presentation.presentableText ?: "", + presentation.locationString ?: "", + (presentation as? PresentationData)?.coloredText ?: emptyList() + ) + for (child in element.children) { + if (child !is StructureViewTreeElement) continue + node.subNode(createActualNode(child)) + } + return node + } + + class LogicalStructureNode( + val icon: Icon?, + val name: String, + val location: String, + val coloredTextElements: List = emptyList(), + ) { + + private val subNodes = mutableListOf() + + fun subNode(subNode: LogicalStructureNode) { + subNodes.add(subNode) + } + + fun node(icon: Icon?, name: String, location: String? = "", initializer: (LogicalStructureNode.() -> Unit)? = null) { + val subNode = LogicalStructureNode(icon, name, location ?: "") + initializer?.invoke(subNode) + subNodes.add(subNode) + } + + fun node(icon: Icon?, vararg coloredText: Pair, initializer: (LogicalStructureNode.() -> Unit)? = null) { + val subNode = LogicalStructureNode(icon, "", "", coloredText.map { PresentableNodeDescriptor.ColoredFragment(it.first, it.second) }) + initializer?.invoke(subNode) + subNodes.add(subNode) + } + + fun isEqualTo(other: LogicalStructureNode, compareItSelf: Boolean, availableDepth: Int = MAX_DEPTH): Boolean { + if (availableDepth <= 0) throw AssertionFailedError("The structure is too deep. Most probably there is cyclic nodes here.") + if (compareItSelf) { + if (coloredTextElements.isNotEmpty()) { + if (coloredTextElements.size != other.coloredTextElements.size) return false + for (i in coloredTextElements.indices) { + if (coloredTextElements[i] != other.coloredTextElements[i]) return false + } + } else { + if (icon != other.icon || name != other.name || location != other.location) return false + } + } + if (subNodes.size != other.subNodes.size) return false + for (i in subNodes.indices) { + if (!subNodes[i].isEqualTo(other.subNodes[i], true, availableDepth - 1)) return false + } + return true + } + + fun print(indent: String = "", printItself: Boolean = true): String { + var result = indent + icon?.toString()?.let { result += "[$it] " } + if (coloredTextElements.isNotEmpty()) { + result += coloredTextElements.joinToString(" | ") { it.text } + } else { + result += name + if (location.isNotEmpty()) result += " | $location" + } + for (subNode in subNodes) { + result += "\n" + subNode.print(if (printItself) "$indent " else indent) + } + if (!printItself) result = result.substringAfter("\n") + return result + } + + } + +} \ No newline at end of file