diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GenerateTestsTabHelper.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GenerateTestsTabHelper.kt index f9ba003b0..00e2f6649 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GenerateTestsTabHelper.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GenerateTestsTabHelper.kt @@ -11,7 +11,10 @@ object GenerateTestsTabHelper { generatedTestsTabData: GeneratedTestsTabData, ) { // Update the number of selected test cases if necessary - if (generatedTestsTabData.testCaseIdToSelectedCheckbox[testCaseId]!!.isSelected) { + val testCaseIsSelected = generatedTestsTabData.testCaseIdToSelectedCheckbox[testCaseId]!!.isSelected + val testCaseIsNotHidden = !generatedTestsTabData.hiddenTestCases.contains(testCaseId) + generatedTestsTabData.hiddenTestCases.remove(testCaseId) + if (testCaseIsSelected && testCaseIsNotHidden) { generatedTestsTabData.testsSelected-- } @@ -28,6 +31,30 @@ object GenerateTestsTabHelper { generatedTestsTabData.testCaseIdToEditorTextField.remove(testCaseId) } + fun showTestCase( + testCaseId: Int, + generatedTestsTabData: GeneratedTestsTabData, + ) { + generatedTestsTabData.hiddenTestCases.remove(testCaseId) + if (generatedTestsTabData.testCaseIdToSelectedCheckbox[testCaseId]!!.isSelected) { + generatedTestsTabData.testsSelected++ + } + + update(generatedTestsTabData) + } + + fun hideTestCase( + testCaseId: Int, + generatedTestsTabData: GeneratedTestsTabData, + ) { + generatedTestsTabData.hiddenTestCases.add(testCaseId) + if (generatedTestsTabData.testCaseIdToSelectedCheckbox[testCaseId]!!.isSelected) { + generatedTestsTabData.testsSelected-- + } + + update(generatedTestsTabData) + } + /** * Updates the user interface of the tool window. * diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabBuilder.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabBuilder.kt index abab70f5e..f70df88b8 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabBuilder.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabBuilder.kt @@ -176,7 +176,7 @@ class GeneratedTestsTabBuilder( testCasePanel.add(Box.createRigidArea(Dimension(12, 0)), BorderLayout.EAST) // Add panel to parent panel - testCasePanel.maximumSize = Dimension(Short.MAX_VALUE.toInt(), Short.MAX_VALUE.toInt()) + testCasePanel.maximumSize = Dimension(Short.MAX_VALUE.toInt(), testCasePanel.preferredSize.height) generatedTestsTabData.allTestCasePanel.add(testCasePanel) addSeparator() @@ -205,7 +205,10 @@ class GeneratedTestsTabBuilder( fun applyTests(): Boolean { // Filter the selected test cases val selectedTestCasePanels = - generatedTestsTabData.testCaseIdToPanel.filter { (it.value.getComponent(0) as JCheckBox).isSelected } + generatedTestsTabData.testCaseIdToPanel.filter { + val testIsNotHidden = !generatedTestsTabData.hiddenTestCases.contains(it.key) + (it.value.getComponent(0) as JCheckBox).isSelected && testIsNotHidden + } val selectedTestCases = selectedTestCasePanels.map { it.key } // Get the test case components (source code of the tests) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabData.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabData.kt index a220e3878..6863f941a 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabData.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/GeneratedTestsTabData.kt @@ -19,6 +19,7 @@ class GeneratedTestsTabData( val testCaseIdToEditorTextField: HashMap = HashMap() var testsSelected: Int = 0 val unselectedTestCases: HashMap = HashMap() + val hiddenTestCases: HashSet = HashSet() val testCasePanelFactories: ArrayList = arrayListOf() var allTestCasePanel: JPanel = JPanel() val applyButton: JButton = JButton(PluginLabelsBundle.get("applyButton")) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TestCasePanelBuilder.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TestCasePanelBuilder.kt index e1a3f7dde..2d8e96c97 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TestCasePanelBuilder.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TestCasePanelBuilder.kt @@ -66,6 +66,7 @@ import org.jetbrains.research.testspark.tools.error.createNotification import org.jetbrains.research.testspark.tools.factories.TestCompilerFactory import org.jetbrains.research.testspark.tools.llm.error.LLMErrorManager import org.jetbrains.research.testspark.tools.llm.test.JUnitTestSuitePresenter +import java.awt.Component import java.awt.Dimension import java.awt.Toolkit import java.awt.datatransfer.Clipboard @@ -82,7 +83,6 @@ import javax.swing.ScrollPaneConstants import javax.swing.SwingUtilities import javax.swing.border.Border import javax.swing.border.MatteBorder -import kotlin.collections.HashMap class TestCasePanelBuilder( private val project: Project, @@ -97,6 +97,9 @@ class TestCasePanelBuilder( private val testsExecutionResultManager: TestsExecutionResultManager, private val generationTool: GenerationTool, ) { + private var visibleTestCasePanel: JPanel? = null + private var hiddenTestCasePanel: JPanel? = null + private val llmSettingsState: LLMSettingsState get() = project.getService(LLMSettingsService::class.java).state @@ -120,6 +123,7 @@ class TestCasePanelBuilder( private val dimensionSize = 7 private var isRemoved = false + private var isShown = true // Add an editor to modify the test source code private val languageTextField = @@ -307,6 +311,7 @@ class TestCasePanelBuilder( runTestButton.isEnabled = true buttonsPanel.add(runTestButton) buttonsPanel.add(Box.createHorizontalGlue()) + buttonsPanel.add(Box.createHorizontalGlue()) resetButton.isEnabled = false buttonsPanel.add(resetButton) resetToLastRunButton.isEnabled = false @@ -331,8 +336,7 @@ class TestCasePanelBuilder( } resetButton.addActionListener { reset() } resetToLastRunButton.addActionListener { resetToLastRun() } - removeButton.addActionListener { remove() } - + removeButton.addActionListener { toggleTestCaseVisibility(false) } sendButton.addActionListener { sendRequest() } /** @@ -345,6 +349,34 @@ class TestCasePanelBuilder( return panel } + private fun getHiddenTestCasePanel(): JPanel { + val panel = JPanel() + panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS) + + val removedTestJLabel = JLabel(PluginLabelsBundle.get("removedTestJLabel")) + val removePermanentlyButton = JButton(PluginLabelsBundle.get("removeTestLabel")) + val undoButton = JButton(PluginLabelsBundle.get("undoButtonLabel")) + + val buttonPanel = JPanel() + buttonPanel.layout = BoxLayout(buttonPanel, BoxLayout.X_AXIS) + buttonPanel.add(Box.createHorizontalGlue()) + buttonPanel.add(removePermanentlyButton) + buttonPanel.add(Box.createRigidArea(Dimension(10, 0))) + buttonPanel.add(undoButton) + buttonPanel.add(Box.createHorizontalGlue()) + + panel.add(Box.createRigidArea(Dimension(0, 10))) + removedTestJLabel.alignmentX = Component.CENTER_ALIGNMENT + panel.add(removedTestJLabel) + panel.add(Box.createRigidArea(Dimension(0, 10))) + panel.add(buttonPanel) + + removePermanentlyButton.addActionListener { removePermanently() } + undoButton.addActionListener { toggleTestCaseVisibility(true) } + + return panel + } + /** * Updates the label displaying the request number information. * Uses the requestNumber template to format the label text. @@ -751,18 +783,46 @@ class TestCasePanelBuilder( * 2. Removing the test case from the cache. * 3. Updating the UI. */ - private fun remove() { + private fun removePermanently() { + val indexOfSeparatorBelowTestCase = + generatedTestsTabData.allTestCasePanel.getComponentZOrder(hiddenTestCasePanel) + 1 + generatedTestsTabData.allTestCasePanel.remove(hiddenTestCasePanel) + generatedTestsTabData.allTestCasePanel.remove(indexOfSeparatorBelowTestCase) + // Remove the test case from the cache GenerateTestsTabHelper.removeTestCase(testCase.id, generatedTestsTabData) runTestButton.isEnabled = false isRemoved = true - ReportUpdater.removeTestCase(report, testCase, coverageVisualisationTabBuilder, generatedTestsTabData) - + if (isShown) { + ReportUpdater.removeTestCase(report, testCase, coverageVisualisationTabBuilder, generatedTestsTabData) + } GenerateTestsTabHelper.update(generatedTestsTabData) } + private fun toggleTestCaseVisibility(visible: Boolean) { + isShown = visible + if (visible) { + val componentIndex = generatedTestsTabData.allTestCasePanel.getComponentZOrder(hiddenTestCasePanel) + generatedTestsTabData.allTestCasePanel.remove(hiddenTestCasePanel) + generatedTestsTabData.allTestCasePanel.add(visibleTestCasePanel, componentIndex) + + GenerateTestsTabHelper.showTestCase(testCase.id, generatedTestsTabData) + ReportUpdater.addTestCase(report, testCase, coverageVisualisationTabBuilder, generatedTestsTabData) + } else { + val currentTestCasePanel = generatedTestsTabData.testCaseIdToPanel[testCase.id]!! + if (visibleTestCasePanel == null) visibleTestCasePanel = currentTestCasePanel + if (hiddenTestCasePanel == null) hiddenTestCasePanel = getHiddenTestCasePanel() + val componentIndex = generatedTestsTabData.allTestCasePanel.getComponentZOrder(currentTestCasePanel) + generatedTestsTabData.allTestCasePanel.remove(currentTestCasePanel) + generatedTestsTabData.allTestCasePanel.add(hiddenTestCasePanel!!, componentIndex) + + GenerateTestsTabHelper.hideTestCase(testCase.id, generatedTestsTabData) + ReportUpdater.removeTestCase(report, testCase, coverageVisualisationTabBuilder, generatedTestsTabData) + } + } + /** * Determines if the "Run" button is enabled. * @@ -828,6 +888,8 @@ class TestCasePanelBuilder( */ fun isRemoved() = isRemoved + fun isShown() = isShown + /** * Updates the current test case with the specified test name and test code. */ diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TopButtonsPanelBuilder.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TopButtonsPanelBuilder.kt index 4fc9c653a..beb082b9f 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TopButtonsPanelBuilder.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/generatedTests/TopButtonsPanelBuilder.kt @@ -42,7 +42,7 @@ class TopButtonsPanelBuilder { fun update(generatedTestsTabData: GeneratedTestsTabData) { val passedTestsCount = generatedTestsTabData.testCasePanelFactories - .filter { !it.isRemoved() } + .filter { it.isRemoved().not() && it.isShown() } .count { it.getError()?.isEmpty() == true } val removedTestsCount = generatedTestsTabData.testCasePanelFactories.count { it.isRemoved() } @@ -51,17 +51,20 @@ class TopButtonsPanelBuilder { removeAllButton.doClick() return } + + val numOfShownTests = + generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size testsSelectedLabel.text = String.format( testsSelectedText, generatedTestsTabData.testsSelected, - generatedTestsTabData.testCaseIdToPanel.size, + numOfShownTests, ) testsPassedLabel.text = String.format( testsPassedText, passedTestsCount, - generatedTestsTabData.testCaseIdToPanel.size, + numOfShownTests, ) runAllButton.isEnabled = false for (testCasePanelFactory in generatedTestsTabData.testCasePanelFactories) { @@ -81,11 +84,18 @@ class TopButtonsPanelBuilder { selected: Boolean, generatedTestsTabData: GeneratedTestsTabData, ) { - generatedTestsTabData.testCaseIdToPanel.forEach { (_, jPanel) -> - val checkBox = jPanel.getComponent(0) as JCheckBox - checkBox.isSelected = selected - } - generatedTestsTabData.testsSelected = if (selected) generatedTestsTabData.testCaseIdToPanel.size else 0 + generatedTestsTabData.testCaseIdToPanel + .filter { it.key !in generatedTestsTabData.hiddenTestCases } + .forEach { (_, jPanel) -> + val checkBox = jPanel.getComponent(0) as JCheckBox + checkBox.isSelected = selected + } + generatedTestsTabData.testsSelected = + if (selected) { + generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size + } else { + 0 + } } /** @@ -115,7 +125,7 @@ class TopButtonsPanelBuilder { val tasks: Queue<(CustomProgressIndicator) -> Unit> = LinkedList() for (testCasePanelFactory in generatedTestsTabData.testCasePanelFactories) { - testCasePanelFactory.addTask(tasks) + if (testCasePanelFactory.isShown()) testCasePanelFactory.addTask(tasks) } // run tasks one after each other executeTasks(project, tasks, generatedTestsTabData) diff --git a/src/main/kotlin/org/jetbrains/research/testspark/display/utils/ReportUpdater.kt b/src/main/kotlin/org/jetbrains/research/testspark/display/utils/ReportUpdater.kt index 5eea34f19..c2386f89b 100644 --- a/src/main/kotlin/org/jetbrains/research/testspark/display/utils/ReportUpdater.kt +++ b/src/main/kotlin/org/jetbrains/research/testspark/display/utils/ReportUpdater.kt @@ -29,6 +29,17 @@ object ReportUpdater { coverageVisualisationTabBuilder.show(report, generatedTestsTabData) } + fun addTestCase( + report: Report, + testCase: TestCase, + coverageVisualisationTabBuilder: CoverageVisualisationTabBuilder, + generatedTestsTabData: GeneratedTestsTabData, + ) { + report.testCaseList[testCase.id] = testCase + report.normalized() + coverageVisualisationTabBuilder.show(report, generatedTestsTabData) + } + fun unselectTestCase( report: Report, unselectedTestCases: HashMap, diff --git a/src/main/resources/properties/plugin/PluginLabels.properties b/src/main/resources/properties/plugin/PluginLabels.properties index 5b7d51dcc..dbad2f777 100644 --- a/src/main/resources/properties/plugin/PluginLabels.properties +++ b/src/main/resources/properties/plugin/PluginLabels.properties @@ -64,4 +64,7 @@ evosuiteSetup=EvoSuite Setup llmSetup=LLM Setup llmSampleSelectorFactory=Test Sample Provider for LLM provideTestSample=Provide test samples -noTestSample=Do not provide any sample \ No newline at end of file +noTestSample=Do not provide any sample +removedTestJLabel=This test was removed +undoButtonLabel=Undo +removeTestLabel=Remove permanently \ No newline at end of file