From e638d2029f9529d81ea37851c5821befaeab241a Mon Sep 17 00:00:00 2001 From: Danylo Biliaiev Date: Thu, 10 Apr 2025 11:46:48 +0200 Subject: [PATCH 1/2] Implement undo delete test case functionality --- .../generatedTests/GenerateTestsTabHelper.kt | 26 +++++- .../GeneratedTestsTabBuilder.kt | 7 +- .../generatedTests/GeneratedTestsTabData.kt | 1 + .../generatedTests/TestCasePanelBuilder.kt | 84 +++++++++++++++---- .../generatedTests/TopButtonsPanelBuilder.kt | 28 ++++--- .../testspark/display/utils/ReportUpdater.kt | 11 +++ .../properties/plugin/PluginLabels.properties | 5 +- 7 files changed, 132 insertions(+), 30 deletions(-) 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..642bbfda1 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,27 @@ 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..e17fd3f6d 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,20 +66,13 @@ 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 import java.awt.datatransfer.StringSelection -import java.util.Queue -import javax.swing.Box -import javax.swing.BoxLayout -import javax.swing.JButton -import javax.swing.JCheckBox -import javax.swing.JLabel -import javax.swing.JOptionPane -import javax.swing.JPanel -import javax.swing.ScrollPaneConstants -import javax.swing.SwingUtilities +import java.util.* +import javax.swing.* import javax.swing.border.Border import javax.swing.border.MatteBorder import kotlin.collections.HashMap @@ -97,6 +90,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 +116,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 +304,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 +329,7 @@ class TestCasePanelBuilder( } resetButton.addActionListener { reset() } resetToLastRunButton.addActionListener { resetToLastRun() } - removeButton.addActionListener { remove() } - + removeButton.addActionListener { toggleTestCaseVisibility(false) } sendButton.addActionListener { sendRequest() } /** @@ -345,6 +342,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 +776,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. * @@ -827,6 +880,7 @@ class TestCasePanelBuilder( * @return true if the item is removed, false otherwise. */ 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..19ab06abb 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,19 @@ class TopButtonsPanelBuilder { removeAllButton.doClick() return } - testsSelectedLabel.text = - String.format( - testsSelectedText, - generatedTestsTabData.testsSelected, - generatedTestsTabData.testCaseIdToPanel.size, - ) + + val numOfShownTests = + generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size + testsSelectedLabel.text = String.format( + testsSelectedText, + generatedTestsTabData.testsSelected, + numOfShownTests, + ) testsPassedLabel.text = String.format( testsPassedText, passedTestsCount, - generatedTestsTabData.testCaseIdToPanel.size, + numOfShownTests, ) runAllButton.isEnabled = false for (testCasePanelFactory in generatedTestsTabData.testCasePanelFactories) { @@ -81,11 +83,15 @@ class TopButtonsPanelBuilder { selected: Boolean, generatedTestsTabData: GeneratedTestsTabData, ) { - generatedTestsTabData.testCaseIdToPanel.forEach { (_, jPanel) -> + 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 else 0 + generatedTestsTabData.testsSelected = if (selected){ + generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size + } else 0 } /** @@ -115,7 +121,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 From 6f327c398dfda17a1d797d482d9210964586f778 Mon Sep 17 00:00:00 2001 From: Danylo Biliaiev Date: Thu, 10 Apr 2025 12:00:30 +0200 Subject: [PATCH 2/2] Fix ktlint issues --- .../generatedTests/GenerateTestsTabHelper.kt | 5 +++- .../generatedTests/TestCasePanelBuilder.kt | 14 +++++++--- .../generatedTests/TopButtonsPanelBuilder.kt | 26 +++++++++++-------- 3 files changed, 30 insertions(+), 15 deletions(-) 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 642bbfda1..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 @@ -43,7 +43,10 @@ object GenerateTestsTabHelper { update(generatedTestsTabData) } - fun hideTestCase(testCaseId: Int, generatedTestsTabData: GeneratedTestsTabData) { + fun hideTestCase( + testCaseId: Int, + generatedTestsTabData: GeneratedTestsTabData, + ) { generatedTestsTabData.hiddenTestCases.add(testCaseId) if (generatedTestsTabData.testCaseIdToSelectedCheckbox[testCaseId]!!.isSelected) { generatedTestsTabData.testsSelected-- 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 e17fd3f6d..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 @@ -71,11 +71,18 @@ import java.awt.Dimension import java.awt.Toolkit import java.awt.datatransfer.Clipboard import java.awt.datatransfer.StringSelection -import java.util.* -import javax.swing.* +import java.util.Queue +import javax.swing.Box +import javax.swing.BoxLayout +import javax.swing.JButton +import javax.swing.JCheckBox +import javax.swing.JLabel +import javax.swing.JOptionPane +import javax.swing.JPanel +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, @@ -880,6 +887,7 @@ class TestCasePanelBuilder( * @return true if the item is removed, false otherwise. */ fun isRemoved() = isRemoved + fun isShown() = isShown /** 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 19ab06abb..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 @@ -54,11 +54,12 @@ class TopButtonsPanelBuilder { val numOfShownTests = generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size - testsSelectedLabel.text = String.format( - testsSelectedText, - generatedTestsTabData.testsSelected, - numOfShownTests, - ) + testsSelectedLabel.text = + String.format( + testsSelectedText, + generatedTestsTabData.testsSelected, + numOfShownTests, + ) testsPassedLabel.text = String.format( testsPassedText, @@ -86,12 +87,15 @@ class TopButtonsPanelBuilder { 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 + val checkBox = jPanel.getComponent(0) as JCheckBox + checkBox.isSelected = selected + } + generatedTestsTabData.testsSelected = + if (selected) { + generatedTestsTabData.testCaseIdToPanel.size - generatedTestsTabData.hiddenTestCases.size + } else { + 0 + } } /**