[terminal] Add mock ShellCommandSpec for testing and Backspace test.

(cherry picked from commit 15b96f9a4eb25b93c89114e1508d8cabc4f628f5)

IJ-MR-170869

GitOrigin-RevId: 4755250f672244c38ea1693e88be118b5b5a4f66
This commit is contained in:
Yuliya Karalenka
2025-08-07 16:16:56 +00:00
committed by intellij-monorepo-bot
parent a1189ae31c
commit da71af67ec
2 changed files with 100 additions and 28 deletions
@@ -12,20 +12,26 @@ import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.terminal.completion.spec.ShellCommandSpec
import com.intellij.terminal.frontend.ReworkedTerminalView
import com.intellij.terminal.frontend.TimedKeyEvent
import com.intellij.terminal.session.TerminalBlocksModelState
import com.intellij.terminal.session.TerminalOutputBlock
import com.intellij.terminal.session.TerminalSession
import com.intellij.terminal.tests.block.util.TestCommandSpecsProvider
import com.intellij.testFramework.ExtensionTestUtil
import org.jetbrains.plugins.terminal.JBTerminalSystemSettingsProvider
import org.jetbrains.plugins.terminal.LocalBlockTerminalRunner.Companion.REWORKED_TERMINAL_COMPLETION_POPUP
import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpecConflictStrategy
import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpecInfo
import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpecsProvider
import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel
import java.awt.event.KeyEvent
import java.awt.event.KeyEvent.VK_UNDEFINED
import java.util.concurrent.CompletableFuture
import kotlin.time.TimeSource
class TerminalCompletionFixture(val project: Project, testRootDisposable: Disposable) {
class TerminalCompletionFixture(val project: Project, val testRootDisposable: Disposable) {
private val view: ReworkedTerminalView
@@ -84,12 +90,8 @@ class TerminalCompletionFixture(val project: Project, testRootDisposable: Dispos
/**
* Simulates a key press in the active completion popup.
* @param keyCode (VK_LEFT or VK_ENTER)
*/
fun pressKey(keycode: Int) {
if (keycode != KeyEvent.VK_LEFT && keycode != KeyEvent.VK_RIGHT) {
throw IllegalArgumentException("keycode must be VK_LEFT or VK_RIGHT")
}
val keyPressEvent = KeyEvent(
view.outputEditor.component,
KeyEvent.KEY_PRESSED,
@@ -100,8 +102,14 @@ class TerminalCompletionFixture(val project: Project, testRootDisposable: Dispos
KeyEvent.KEY_LOCATION_STANDARD
)
view.outputEditorEventsHandler.keyPressed(TimedKeyEvent(keyPressEvent, TimeSource.Monotonic.markNow()))
val commonOffset = view.outputEditor.getCaretModel().offset
view.outputModel.updateCursorPosition(view.outputModel.relativeOffset(commonOffset))
val offset = view.outputModel.cursorOffsetState.value
val newOffset = when (keycode) {
KeyEvent.VK_LEFT -> offset - 1
KeyEvent.VK_RIGHT -> offset + 1
KeyEvent.VK_BACK_SPACE -> offset - 1
else -> offset
}
view.outputModel.updateCursorPosition(view.outputModel.relativeOffset(newOffset))
}
private fun runActionById(actionId: String) {
@@ -118,4 +126,11 @@ class TerminalCompletionFixture(val project: Project, testRootDisposable: Dispos
ActionUtil.performAction(action, event)
}
}
fun mockTestShellCommand(testCommandSpec: ShellCommandSpec) {
val specsProvider: ShellCommandSpecsProvider = TestCommandSpecsProvider(
ShellCommandSpecInfo.create(testCommandSpec, ShellCommandSpecConflictStrategy.DEFAULT)
)
ExtensionTestUtil.maskExtensions(ShellCommandSpecsProvider.EP_NAME, listOf(specsProvider), testRootDisposable)
}
}
@@ -1,39 +1,62 @@
package com.intellij.terminal.tests.reworked.frontend
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpec
import org.junit.Assert.assertNotEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.awt.event.KeyEvent.VK_LEFT
import java.awt.event.KeyEvent.VK_RIGHT
import java.awt.event.KeyEvent.*
@RunWith(JUnit4::class)
class TerminalCompletionTest : BasePlatformTestCase() {
val testCommandSpec = ShellCommandSpec("test_cmd") {
subcommands {
subcommand("status")
subcommand("start")
subcommand("stop")
subcommand("set")
subcommand("sync")
subcommand("show")
}
subcommands {
subcommand("build")
subcommand("bind")
subcommand("branch")
}
}
@Test
fun `test completions list filtered on typing`() {
val terminalCompletionFixture = TerminalCompletionFixture(project, testRootDisposable)
terminalCompletionFixture.type("git s")
terminalCompletionFixture.mockTestShellCommand(testCommandSpec)
terminalCompletionFixture.type("test_cmd ")
terminalCompletionFixture.callCompletionPopup()
assertEquals(true, terminalCompletionFixture.isLookupActive())
val beforeResult = terminalCompletionFixture.getLookupElements()
val beforeResultStrings = beforeResult.map { it.lookupString }
assertEquals(listOf("show", "stage", "stash", "status", "submodule", "switch"), beforeResultStrings)
assertSameElements(listOf("bind", "branch", "build", "set", "show", "start", "status", "stop", "sync"),
beforeResultStrings)
terminalCompletionFixture.type("t")
terminalCompletionFixture.type("s")
val result = terminalCompletionFixture.getLookupElements()
val resultStrings = result.map { it.lookupString }
assertEquals(listOf("stage", "stash", "status"), resultStrings)
assertSameElements(listOf("set", "show", "start", "status", "stop", "sync"), resultStrings)
terminalCompletionFixture.type("t")
assertSameElements(listOf("start", "status", "stop"), terminalCompletionFixture.getLookupElements().map { it.lookupString })
}
@Test
fun `test terminal completion up and down`() {
fun `test selection returns to original item after down and up actions`() {
val terminalCompletionFixture = TerminalCompletionFixture(project, testRootDisposable)
terminalCompletionFixture.type("git b")
terminalCompletionFixture.mockTestShellCommand(testCommandSpec)
terminalCompletionFixture.type("test_cmd ")
terminalCompletionFixture.callCompletionPopup()
val startResult = terminalCompletionFixture.getLookupElements()
assertEquals(3, startResult.size)
assertSameElements(listOf("bind", "branch", "build", "set", "show", "start", "status", "stop", "sync"),
terminalCompletionFixture.getLookupElements().map { it.lookupString })
val firstElement = terminalCompletionFixture.getCurrentItem()
terminalCompletionFixture.downCompletionPopup()
@@ -46,41 +69,75 @@ class TerminalCompletionTest : BasePlatformTestCase() {
}
@Test
fun `test terminal completion with left right`() {
fun `test completion list is re-filtered when caret moves over prefix to the left, right`() {
val terminalCompletionFixture = TerminalCompletionFixture(project, testRootDisposable)
terminalCompletionFixture.type("git ")
terminalCompletionFixture.mockTestShellCommand(testCommandSpec)
terminalCompletionFixture.type("test_cmd ")
terminalCompletionFixture.callCompletionPopup()
terminalCompletionFixture.type("st")
val startResult = terminalCompletionFixture.getLookupElements()
assertSameElements(listOf("start", "status", "stop"), startResult.map { it.lookupString })
terminalCompletionFixture.pressKey(VK_LEFT)
val afterFirstLeftResult = terminalCompletionFixture.getLookupElements()
assertSameElements(listOf("set", "show", "start", "status", "stop", "sync"),
afterFirstLeftResult.map { it.lookupString })
terminalCompletionFixture.pressKey(VK_LEFT)
val afterSecondLeftResult = terminalCompletionFixture.getLookupElements()
assertNotEquals(afterFirstLeftResult.size, afterSecondLeftResult.size)
assertSameElements(listOf("bind", "branch", "build", "set", "show", "start", "status", "stop", "sync"),
afterSecondLeftResult.map { it.lookupString })
terminalCompletionFixture.pressKey(VK_RIGHT)
val afterRightResult = terminalCompletionFixture.getLookupElements()
assertEquals(afterFirstLeftResult.size, afterRightResult.size)
assertEquals(afterFirstLeftResult, afterRightResult)
terminalCompletionFixture.pressKey(VK_RIGHT)
val afterSecondRightResult = terminalCompletionFixture.getLookupElements()
assertEquals(startResult.size, afterSecondRightResult.size)
assertEquals(startResult, afterSecondRightResult)
}
@Test
fun `test terminal completion with left right reopening`() {
fun `test lookup remains active when caret moves into text typed before call popup`() {
val terminalCompletionFixture = TerminalCompletionFixture(project, testRootDisposable)
terminalCompletionFixture.type("git st")
terminalCompletionFixture.mockTestShellCommand(testCommandSpec)
terminalCompletionFixture.type("test_cmd st")
terminalCompletionFixture.callCompletionPopup()
val startResult = terminalCompletionFixture.getLookupElements()
assertEquals(3, startResult.size)
assertSameElements(listOf("start", "status", "stop"),
startResult.map { it.lookupString })
terminalCompletionFixture.pressKey(VK_LEFT)
val afterLeftResult = terminalCompletionFixture.getLookupElements()
assertEquals(6, afterLeftResult.size)
assertSameElements(listOf("set", "show", "start", "status", "stop", "sync"),
terminalCompletionFixture.getLookupElements().map { it.lookupString })
terminalCompletionFixture.pressKey(VK_RIGHT)
assertSameElements(startResult.map { it.lookupString },
terminalCompletionFixture.getLookupElements().map { it.lookupString })
}
@Test
fun `test completion list is correctly re-filtered after pressing backspace`() {
val terminalCompletionFixture = TerminalCompletionFixture(project, testRootDisposable)
terminalCompletionFixture.mockTestShellCommand(testCommandSpec)
terminalCompletionFixture.type("test_cmd st")
terminalCompletionFixture.callCompletionPopup()
val startResult = terminalCompletionFixture.getLookupElements()
assertSameElements(listOf("start", "status", "stop"),
startResult.map { it.lookupString })
terminalCompletionFixture.type("a")
assertSameElements(listOf("start", "status"),
terminalCompletionFixture.getLookupElements().map { it.lookupString })
terminalCompletionFixture.pressKey(VK_BACK_SPACE)
assertSameElements(listOf("start", "status", "stop"),
terminalCompletionFixture.getLookupElements().map { it.lookupString })
terminalCompletionFixture.pressKey(VK_BACK_SPACE)
assertSameElements(listOf("set", "show", "start", "status", "stop", "sync"),
terminalCompletionFixture.getLookupElements().map { it.lookupString })
}
}