Skip to content

Instantly share code, notes, and snippets.

@dkandalov
Last active June 29, 2024 13:54
Show Gist options
  • Save dkandalov/765232732a0ef7d2937861fcb035b804 to your computer and use it in GitHub Desktop.
Save dkandalov/765232732a0ef7d2937861fcb035b804 to your computer and use it in GitHub Desktop.
Mini-plugin for IntelliJ to show a proper tests progress bar in presentation mode (run it with https://github.com/dkandalov/live-plugin)
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
import com.intellij.execution.testframework.sm.runner.SMTestProxy
import com.intellij.openapi.Disposable
import com.intellij.openapi.progress.util.ColorProgressBar
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.Balloon.Position.above
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
import com.intellij.openapi.wm.ex.ToolWindowManagerListener.ToolWindowManagerEventType
import com.intellij.ui.Gray
import com.intellij.ui.TransparentPanel
import com.intellij.ui.awt.RelativePoint
import com.intellij.util.ui.JBInsets
import liveplugin.currentEditor
import liveplugin.newDisposable
import liveplugin.registerParent
import liveplugin.runLaterOnEdt
import java.awt.Dimension
import java.awt.Point
import javax.swing.JProgressBar
TestsWatcher(project!!, pluginDisposable).start(
listeners = listOf(TestsProgress(project!!, pluginDisposable))
)
class TestsProgress(private val project: Project, private val parentDisposable: Disposable) : TestsWatcher.Listener {
private lateinit var progressPanel: TestsProgressPanel
override fun onTestsStarted() {
progressPanel = TestsProgressPanel(project, parentDisposable)
runLaterOnEdt { progressPanel.show() }
}
override fun onTestAdded(count: Int) {
progressPanel.progressBar.maximum = count
}
override fun onTestFinished(count: Int) {
progressPanel.progressBar.model.value = count
}
override fun onTestFailed() {
progressPanel.progressBar.foreground = ColorProgressBar.RED
}
override fun onTestsSucceeded() = progressPanel.hide()
override fun onTestsFailed() = progressPanel.hide()
override fun onTestsStopped() = progressPanel.hide()
private class TestsProgressPanel(private val project: Project, parentDisposable: Disposable) {
private val disposable = newDisposable().registerParent(parentDisposable)
val progressBar = JProgressBar(0, 100)
fun show(): TestsProgressPanel {
progressBar.apply {
foreground = ColorProgressBar.GREEN
preferredSize = Dimension(500, 20)
}
val panel = TransparentPanel(0.5f).apply {
add(progressBar)
}
val balloon = JBPopupFactory.getInstance().createBalloonBuilder(panel)
.setFadeoutTime(0)
.setFillColor(Gray.TRANSPARENT)
.setShowCallout(false)
.setBorderColor(Gray.TRANSPARENT)
.setBorderInsets(JBInsets(0, 0, 0, 0))
.setAnimationCycle(0)
.setCloseButtonEnabled(false)
.setHideOnClickOutside(false)
.setDisposable(disposable)
.setHideOnFrameResize(false)
.setHideOnKeyOutside(false)
.setBlockClicksThroughBalloon(true)
.setHideOnAction(false)
.setShadow(false)
.createBalloon()
val editorComponent = project.currentEditor!!.component
balloon.show(RelativePoint(editorComponent, Point(editorComponent.width / 2, editorComponent.height - 150)), above)
project.messageBus.connect(disposable).subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener {
override fun stateChanged(toolWindowManager: ToolWindowManager, changeType: ToolWindowManagerEventType) {
balloon.revalidate()
}
})
return this
}
fun hide() {
Disposer.dispose(disposable)
}
}
}
class TestsWatcher(private val project: Project, private val disposable: Disposable) {
fun start(listeners: List<Listener>) {
var totalTestCount = 0
var testsFinished = 0
var testsFailed = 0
val listener = object : SMTRunnerEventsAdapter() {
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
totalTestCount = 0
testsFinished = 0
testsFailed = 0
listeners.forEach { it.onTestsStarted() }
}
// Not called when running tests via Gradle
override fun onSuiteTreeNodeAdded(testProxy: SMTestProxy?) {
totalTestCount++
listeners.forEach { it.onTestAdded(totalTestCount) }
}
override fun onTestStarted(test: SMTestProxy) {
if (totalTestCount == 0) {
// This is a workaround for Gradle runner which doesn't seem to have a way to know the amount of tests
listeners.forEach { it.onTestAdded(test.parent.allTests.size) }
}
}
override fun onTestFailed(test: SMTestProxy) {
testsFailed++
listeners.forEach { it.onTestFailed() }
}
override fun onTestFinished(test: SMTestProxy) {
testsFinished++
listeners.forEach { it.onTestFinished(testsFinished) }
}
override fun onTestingFinished(testsRoot: SMTestProxy.SMRootTestProxy) {
if (totalTestCount != 0 && testsFinished != totalTestCount) {
listeners.forEach { it.onTestsStopped() }
} else if (testsFailed > 0) {
listeners.forEach { it.onTestsFailed() }
} else {
listeners.forEach { it.onTestsSucceeded() }
}
}
}
project.messageBus.connect(disposable)
.subscribe(SMTRunnerEventsListener.TEST_STATUS, listener)
}
interface Listener {
fun onTestsStarted() {}
fun onTestAdded(count: Int) {}
fun onTestFinished(count: Int) {}
fun onTestFailed() {}
fun onTestsSucceeded() {}
fun onTestsFailed() {}
fun onTestsStopped() {}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment