diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt index d468097d255..88b055a41b8 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/CodeModernizerSession.kt @@ -51,6 +51,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.utils.STATES_AFTE import software.aws.toolkits.jetbrains.services.codemodernizer.utils.calculateTotalLatency import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getModuleOrProjectNameForFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getPathToHilDependencyReportDir +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isPlanComplete import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isValidCodeTransformConnection import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan import software.aws.toolkits.jetbrains.services.codemodernizer.utils.toTransformationLanguage @@ -477,10 +478,12 @@ class CodeModernizerSession( } } - // Open the transformation plan detail panel once transformation plan is available (no plan for SQL conversions) - if (transformType != CodeTransformType.SQL_CONVERSION && state.transformationPlan != null && !isTransformationPlanEditorOpened) { - tryOpenTransformationPlanEditor() - isTransformationPlanEditorOpened = true + if (!isTransformationPlanEditorOpened && transformType == CodeTransformType.LANGUAGE_UPGRADE) { + val isPlanComplete = isPlanComplete(state.transformationPlan) + if (isPlanComplete) { + tryOpenTransformationPlanEditor() + isTransformationPlanEditorOpened = true + } } val instant = Instant.now() // Set the job start time diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTable.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTable.kt index ef1784b9e7c..781ef05ca03 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTable.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/PlanTable.kt @@ -11,7 +11,7 @@ data class PlanTable( @JsonProperty("columnNames") val columns: List, @JsonProperty("rows") - val rows: List, + val rows: MutableList, @JsonProperty("name") val name: String, ) diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt index e65b08f7e12..9fb6b14a380 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/model/ZipManifest.kt @@ -11,6 +11,7 @@ data class ZipManifest( val version: String = UPLOAD_ZIP_MANIFEST_VERSION, val hilCapabilities: List = listOf(HIL_1P_UPGRADE_CAPABILITY), // TODO: add CLIENT_SIDE_BUILD to transformCapabilities when releasing CSB + // TODO: add AGENTIC_PLAN_V1 or something here AND in processCodeTransformSkipTests when backend allowlists everyone val transformCapabilities: List = listOf(EXPLAINABILITY_V1), val customBuildCommand: String = MAVEN_BUILD_RUN_UNIT_TESTS, val requestedConversions: RequestedConversions? = null, // only used for SQL conversions for now diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt index 0d51765749d..eec38b73276 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/panels/managers/CodeModernizerBottomWindowPanelManager.kt @@ -28,6 +28,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.panels.CodeModern import software.aws.toolkits.jetbrains.services.codemodernizer.panels.LoadingPanel import software.aws.toolkits.jetbrains.services.codemodernizer.state.CodeModernizerSessionState import software.aws.toolkits.jetbrains.services.codemodernizer.toolwindow.CodeModernizerBottomToolWindowFactory +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isPlanComplete import software.aws.toolkits.resources.message import java.awt.BorderLayout import java.awt.Component @@ -251,7 +252,7 @@ class CodeModernizerBottomWindowPanelManager(private val project: Project) : JPa TransformationStatus.PAUSED, TransformationStatus.COMPLETED, TransformationStatus.PARTIALLY_COMPLETED - ) && transformType != CodeTransformType.SQL_CONVERSION // no plan for SQL conversions + ) && transformType == CodeTransformType.LANGUAGE_UPGRADE && isPlanComplete(plan) ) { addPlanToBanner() } diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt index 74202e35f11..11372b85283 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt @@ -24,6 +24,7 @@ import software.aws.toolkits.jetbrains.services.codemodernizer.constants.LOC_THR import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact.Companion.MAPPER import software.aws.toolkits.jetbrains.services.codemodernizer.model.PlanTable import software.aws.toolkits.jetbrains.services.codemodernizer.plan.CodeModernizerPlanEditorProvider.Companion.MIGRATION_PLAN_KEY +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.combineTableRows import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getAuthType import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getBillingText import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getLinesOfCodeSubmitted @@ -76,7 +77,7 @@ class CodeModernizerPlanEditor(val project: Project, private val virtualFile: Vi // comes from "name" field of each progressUpdate in step zero of plan if (JOB_STATISTICS_TABLE_KEY in tableMapping) { val planTable = parseTableMapping(tableMapping) - val linesOfCode = planTable?.let { getLinesOfCodeSubmitted(it) } + val linesOfCode = getLinesOfCodeSubmitted(planTable) if (linesOfCode != null && linesOfCode > LOC_THRESHOLD && getAuthType(project) == CredentialSourceId.IamIdentityCenter) { val billingText = getBillingText(linesOfCode) val billingTextComponent = @@ -98,15 +99,15 @@ class CodeModernizerPlanEditor(val project: Project, private val virtualFile: Vi add(billingTextComponent, CodeModernizerUIConstants.transformationPlanPlaneConstraint) } add( - planTable?.let { transformationPlanInfo(it) }, + transformationPlanInfo(planTable), CodeModernizerUIConstants.transformationPlanPlaneConstraint, ) } add(transformationPlanPanel(plan), CodeModernizerUIConstants.transformationPlanPlaneConstraint) - // key "-1" reserved for appendix table + // key "-1" reserved for appendix table; only 1 table there if (APPENDIX_TABLE_KEY in tableMapping) { add( - tableMapping[APPENDIX_TABLE_KEY]?.let { MAPPER.readValue(it) }?.let { transformationPlanAppendix(it) }, + tableMapping[APPENDIX_TABLE_KEY]?.get(0)?.let { MAPPER.readValue(it) }?.let { transformationPlanAppendix(it) }, CodeModernizerUIConstants.transformationPlanPlaneConstraint, ) } @@ -393,10 +394,17 @@ class CodeModernizerPlanEditor(val project: Project, private val virtualFile: Vi border = CodeModernizerUIConstants.DESCRIPTION_BORDER } - val table = tableMapping[step.id()] + val tables = tableMapping[step.id()] - var parsedTable = table?.let { - MAPPER.readValue(it) + val parsedTables = tables?.map { table -> + MAPPER.readValue(table) + } + + var parsedTable: PlanTable? = if (parsedTables?.size == 1) { + parsedTables.first() + } else { + // for multiple tables under 1 step, the table headers are the same, so combine the rows and just show 1 combined table + combineTableRows(parsedTables) } if (parsedTable?.rows?.isEmpty() == true) { diff --git a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt index 6495508328b..730ab8ea267 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/utils/CodeTransformApiUtils.kt @@ -4,6 +4,7 @@ package software.aws.toolkits.jetbrains.services.codemodernizer.utils import com.fasterxml.jackson.module.kotlin.readValue +import com.intellij.grazie.utils.orFalse import com.intellij.notification.NotificationAction import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runWriteAction @@ -285,20 +286,36 @@ fun findDownloadArtifactProgressUpdate(transformationSteps: List update.name() == "1" }.orFalse() + // "name" holds the ID of the corresponding plan step (where table will go) and "description" holds the plan data -fun getTableMapping(stepZeroProgressUpdates: List): Map { - if (stepZeroProgressUpdates.isNotEmpty()) { - return stepZeroProgressUpdates.associate { - it.name() to it.description() +fun getTableMapping(stepZeroProgressUpdates: List): Map> = + stepZeroProgressUpdates.groupBy( + { it.name() }, + { it.description() } + ) + +// ID of '0' reserved for job statistics table; only 1 table there +fun parseTableMapping(tableMapping: Map>): PlanTable { + val statsTable = tableMapping[JOB_STATISTICS_TABLE_KEY]?.get(0) ?: error("No transformation statistics table found in GetPlan response") + return MAPPER.readValue(statsTable) +} + +// columns and name are shared between all PlanTables, so just combine the rows here +fun combineTableRows(tables: List?): PlanTable? { + if (tables == null) { + return null + } + val combinedTable = PlanTable(tables.first().columns, mutableListOf(), tables.first().name) + tables.forEach { table -> + table.rows.forEach { row -> + combinedTable.rows.add(row) } - } else { - error("GetPlan response missing step 0 progress updates with table data") } + return combinedTable } -fun parseTableMapping(tableMapping: Map): PlanTable? = - tableMapping[JOB_STATISTICS_TABLE_KEY]?.let { MAPPER.readValue(it) } - fun getBillingText(linesOfCode: Int): String { val estimatedCost = String.format(Locale.US, "%.2f", linesOfCode.times(BILLING_RATE)) return message("codemodernizer.migration_plan.header.billing_text", linesOfCode, BILLING_RATE, estimatedCost) diff --git a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt index 37482669432..a257245a23c 100644 --- a/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt +++ b/plugins/amazonq/codetransform/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codemodernizer/CodeWhispererCodeModernizerUtilsTest.kt @@ -3,6 +3,7 @@ package software.aws.toolkits.jetbrains.services.codemodernizer +import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.testFramework.LightVirtualFile import io.mockk.every import io.mockk.just @@ -26,11 +27,15 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Transformation import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStatus import software.amazon.awssdk.services.codewhispererruntime.model.TransformationStep import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException +import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeModernizerArtifact.Companion.MAPPER import software.aws.toolkits.jetbrains.services.codemodernizer.model.CodeTransformType +import software.aws.toolkits.jetbrains.services.codemodernizer.model.PlanTable +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.combineTableRows import software.aws.toolkits.jetbrains.services.codemodernizer.utils.createClientSideBuildUploadZip import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getBillingText import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getClientInstructionArtifactId import software.aws.toolkits.jetbrains.services.codemodernizer.utils.getTableMapping +import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isPlanComplete import software.aws.toolkits.jetbrains.services.codemodernizer.utils.parseBuildFile import software.aws.toolkits.jetbrains.services.codemodernizer.utils.pollTransformationStatusAndPlan import software.aws.toolkits.jetbrains.services.codemodernizer.utils.refreshToken @@ -224,10 +229,73 @@ class CodeWhispererCodeModernizerUtilsTest : CodeWhispererCodeModernizerTestBase val step0Update2 = TransformationProgressUpdate.builder().name("2").status("COMPLETED").description(apiChanges).build() val step0Update3 = TransformationProgressUpdate.builder().name("-1").status("COMPLETED").description(fileChanges).build() val actual = getTableMapping(listOf(step0Update0, step0Update1, step0Update2, step0Update3)) - val expected = mapOf("0" to jobStats, "1" to depChanges, "2" to apiChanges, "-1" to fileChanges) + val expected = mapOf("0" to listOf(jobStats), "1" to listOf(depChanges), "2" to listOf(apiChanges), "-1" to listOf(fileChanges)) assertThat(expected).isEqualTo(actual) } + @Test + fun `combineTableRows combines multiple dependency tables correctly`() { + val table1Json = """ + {"name":"Dependency changes", "columnNames":["dependencyName","action","currentVersion","targetVersion"], + "rows":[{"dependencyName":"org.springframework.boot","action":"Update","currentVersion":"2.1","targetVersion":"2.4"}]} + """.trimIndent() + val table2Json = """ + {"name":"Dependency changes", "columnNames":["dependencyName","action","currentVersion","targetVersion"], + "rows":[{"dependencyName":"junit","action":"Add","currentVersion":"","targetVersion":"4.13"}]} + """.trimIndent() + val tables = listOf( + MAPPER.readValue(table1Json), + MAPPER.readValue(table2Json) + ) + val combinedTable = combineTableRows(tables) + assertThat(combinedTable?.rows).hasSize(2) + assertThat(combinedTable?.name).isEqualTo("Dependency changes") + assertThat(combinedTable?.columns).hasSize(4) + } + + @Test + fun `isPlanComplete returns true when plan has progress update with name '1'`() { + // Arrange + val plan = TransformationPlan.builder() + .transformationSteps( + listOf( + TransformationStep.builder() + .progressUpdates( + listOf( + TransformationProgressUpdate.builder() + .name("1") + .build() + ) + ) + .build() + ) + ) + .build() + val result = isPlanComplete(plan) + assertThat(result).isTrue() + } + + @Test + fun `isPlanComplete returns false when plan has no progress update with name '1'`() { + val plan = TransformationPlan.builder() + .transformationSteps( + listOf( + TransformationStep.builder() + .progressUpdates( + listOf( + TransformationProgressUpdate.builder() + .name("2") + .build() + ) + ) + .build() + ) + ) + .build() + val result = isPlanComplete(plan) + assertThat(result).isFalse() + } + @Test fun `getClientInstructionArtifactId extracts artifact ID from transformation plan`() { val step1 = TransformationStep.builder() diff --git a/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/inlineTests/AmazonQInlineCompletionE2ETest.kt b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/inlineTests/AmazonQInlineCompletionE2ETest.kt new file mode 100644 index 00000000000..d8b84e713ed --- /dev/null +++ b/ui-tests-starter/tst-243+/software/aws/toolkits/jetbrains/uitests/inlineTests/AmazonQInlineCompletionE2ETest.kt @@ -0,0 +1,343 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.uitests.inlineTests + +import com.intellij.driver.sdk.openFile +import com.intellij.driver.sdk.step +import com.intellij.driver.sdk.ui.components.common.editor +import com.intellij.driver.sdk.ui.components.common.ideFrame +import com.intellij.driver.sdk.ui.ui +import com.intellij.driver.sdk.waitForProjectOpen +import com.intellij.ide.starter.ci.CIServer +import com.intellij.ide.starter.config.ConfigurationStorage +import com.intellij.ide.starter.di.di +import com.intellij.ide.starter.driver.engine.runIdeWithDriver +import com.intellij.ide.starter.ide.IdeProductProvider +import com.intellij.ide.starter.junit5.hyphenateWithClass +import com.intellij.ide.starter.models.TestCase +import com.intellij.ide.starter.project.LocalProjectInfo +import com.intellij.ide.starter.runner.CurrentTestMethod +import com.intellij.ide.starter.runner.Starter +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.kodein.di.DI +import org.kodein.di.bindSingleton +import software.aws.toolkits.jetbrains.uitests.TestCIServer +import software.aws.toolkits.jetbrains.uitests.useExistingConnectionForTest +import java.awt.event.KeyEvent +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardOpenOption + +class AmazonQInlineCompletionE2ETest { + private val originalContent = """public class MathClass { + public static void main(String[] args) { + int a = 10; + int b = 20; + int c = add(a, b); + System.out.println("The sum of a and b is: " + c); + } + +}""" + + init { + di = DI { + extend(di) + bindSingleton(overrides = true) { TestCIServer } + val defaults = ConfigurationStorage.instance().defaults.toMutableMap().apply { + put("LOG_ENVIRONMENT_VARIABLES", (!System.getenv("CI").toBoolean()).toString()) + } + + bindSingleton(overrides = true) { + ConfigurationStorage(this, defaults) + } + } + resetTestFile() + } + + @BeforeEach + fun resetTestFile() { + val path = Paths.get("tstData", "inlineCompletionProject", "MathClass.java") + + Files.createDirectories(path.parent) + Files.write( + path, + originalContent.toByteArray(), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ) + } + + @Test + fun `test inline completion functionality`() { + val testCase = TestCase( + IdeProductProvider.IC, + LocalProjectInfo( + Paths.get("tstData", "InlineCompletionProject") + ) + ).withVersion(System.getProperty("org.gradle.project.ideProfileName")) + + useExistingConnectionForTest() + + Starter.newContext(CurrentTestMethod.hyphenateWithClass(), testCase).apply { + System.getProperty("ui.test.plugins").split(File.pathSeparator).forEach { path -> + pluginConfigurator.installPluginFromPath( + Path.of(path) + ) + } + copyExistingConfig(Paths.get("tstData", "configAmazonQTests")) + updateGeneralSettings() + }.runIdeWithDriver() + .useDriverAndCloseIde { + waitForProjectOpen() + Thread.sleep(2000) + + step("Test manual invoke and accept") { + var originalText: String? = null + var afterSuggestion: String? = null + + ideFrame { + openFile("MathClass.java") + editor { + originalText = text + moveCaretToOffset(text.length - 2) + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + ui.keyboard { + key(KeyEvent.VK_TAB) + } + afterSuggestion = text + text = originalContent + } + } + assertThat(afterSuggestion).isNotEqualTo(originalText) + } + + step("Test manual trigger with rejection") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + ui.keyboard { + key(KeyEvent.VK_ESCAPE) + } + + val hintGone = editor.getInlayModel().getInlineElementsInRange(0, text.length).isEmpty() + assertThat(hintGone).isTrue() + text = originalContent + } + } + } + + step("Test manual trigger with discard") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + goToLine(getCaretLine() - 1) + + val hintGone = editor.getInlayModel().getInlineElementsInRange(0, text.length).isEmpty() + assertThat(hintGone).isTrue() + text = originalContent + } + } + } + + step("Test auto trigger with acceptance") { + var originalText: String? = null + var afterSuggestion: String? = null + + ideFrame { + openFile("MathClass.java") + editor { + originalText = text + moveCaretToOffset(text.length - 2) + + ui.keyboard { + key(KeyEvent.VK_ENTER) + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + ui.keyboard { + key(KeyEvent.VK_TAB) + } + afterSuggestion = text + text = originalContent + } + } + assertThat(afterSuggestion).isNotEqualTo(originalText) + } + + step("Test auto trigger with rejection") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + key(KeyEvent.VK_ENTER) + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + ui.keyboard { + key(KeyEvent.VK_ESCAPE) + } + + val hintGone = editor.getInlayModel().getInlineElementsInRange(0, text.length).isEmpty() + assertThat(hintGone).isTrue() + text = originalContent + } + } + } + + step("Test auto trigger with discard") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + key(KeyEvent.VK_ENTER) + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isTrue() + + goToLine(getCaretLine() - 1) + + val hintGone = editor.getInlayModel().getInlineElementsInRange(0, text.length).isEmpty() + assertThat(hintGone).isTrue() + text = originalContent + } + } + } + + step("Test suggestion navigation") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val initialHints = editor.getInlayModel().getInlineElementsInRange(0, text.length) + assertThat(initialHints).isNotEmpty() + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_CLOSE_BRACKET) + } + } + + val newHints = editor.getInlayModel().getInlineElementsInRange(0, text.length) + assertThat(newHints).isNotEqualTo(initialHints) + text = originalContent + } + } + } + + step("Test completion in unsupported file type") { + ideFrame { + openFile("nonsense.xyz") + editor { + moveCaretToOffset(text.length - 2) + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val hintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExists).isFalse() + + ui.keyboard { + key(KeyEvent.VK_ENTER) + } + Thread.sleep(1000) + + val hintExistsAfterAuto = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExistsAfterAuto).isFalse() + text = originalContent + } + } + } + + step("Test typeahead behavior") { + ideFrame { + openFile("MathClass.java") + editor { + moveCaretToOffset(text.length - 2) + + ui.keyboard { + pressing(KeyEvent.VK_ALT) { + key(KeyEvent.VK_C) + } + } + Thread.sleep(1000) + + val initialHintExists = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(initialHintExists).isTrue() + + ui.keyboard { + key(KeyEvent.VK_P) + key(KeyEvent.VK_U) + key(KeyEvent.VK_B) + } + + val hintExistsAfterTyping = editor.getInlayModel().getInlineElementsInRange(0, text.length).isNotEmpty() + assertThat(hintExistsAfterTyping).isTrue() + text = originalContent + } + } + } + } + } +} diff --git a/ui-tests-starter/tstData/inlineCompletionProject/MathClass.java b/ui-tests-starter/tstData/inlineCompletionProject/MathClass.java new file mode 100644 index 00000000000..c06f6d8d030 --- /dev/null +++ b/ui-tests-starter/tstData/inlineCompletionProject/MathClass.java @@ -0,0 +1,9 @@ +public class MathClass { + public static void main(String[] args) { + int a = 10; + int b = 20; + int c = add(a, b); + System.out.println("The sum of a and b is: " + c); + } + +} diff --git a/ui-tests-starter/tstData/inlineCompletionProject/nonsense.xyz b/ui-tests-starter/tstData/inlineCompletionProject/nonsense.xyz new file mode 100644 index 00000000000..c06f6d8d030 --- /dev/null +++ b/ui-tests-starter/tstData/inlineCompletionProject/nonsense.xyz @@ -0,0 +1,9 @@ +public class MathClass { + public static void main(String[] args) { + int a = 10; + int b = 20; + int c = add(a, b); + System.out.println("The sum of a and b is: " + c); + } + +}