diff --git a/plugins/maven/src/test/BUILD.bazel b/plugins/maven/src/test/BUILD.bazel
index 0af1114faea9..686471775d15 100644
--- a/plugins/maven/src/test/BUILD.bazel
+++ b/plugins/maven/src/test/BUILD.bazel
@@ -15,7 +15,6 @@ jvm_library(
# do not sort,
"//platform/vcs-api:vcs",
"//platform/util/progress",
- "//plugins/repository-search/common",
],
)
@@ -178,6 +177,8 @@ jvm_library(
"//platform/execution:execution_test_lib",
"//plugins/repository-search/common",
"//plugins/repository-search/common:common_test_lib",
+ "//plugins/repository-search/completion",
+ "//plugins/repository-search/completion:completion_test_lib",
"//plugins/maven/completion",
"//plugins/maven/completion:completion_test_lib",
"//plugins/maven/jps/ide",
diff --git a/plugins/maven/src/test/intellij.maven.tests.iml b/plugins/maven/src/test/intellij.maven.tests.iml
index e5d8c2e2669a..3ff2567688dd 100644
--- a/plugins/maven/src/test/intellij.maven.tests.iml
+++ b/plugins/maven/src/test/intellij.maven.tests.iml
@@ -75,7 +75,8 @@
-
+
+
diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenDependencyCompletionCharFilterTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenDependencyCompletionCharFilterTest.kt
new file mode 100644
index 000000000000..e7110430d318
--- /dev/null
+++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenDependencyCompletionCharFilterTest.kt
@@ -0,0 +1,172 @@
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.idea.maven.dom
+
+import com.intellij.codeInsight.completion.CompletionType
+import com.intellij.codeInsight.lookup.LookupManager
+import com.intellij.codeInsight.lookup.impl.LookupImpl
+import com.intellij.maven.testFramework.fixtures.MavenDomTestFixture
+import com.intellij.maven.testFramework.fixtures.configTest
+import com.intellij.maven.testFramework.fixtures.mavenDomFixture
+import com.intellij.maven.testFramework.fixtures.updateProjectPom
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.EDT
+import com.intellij.repository.search.completion.api.DependencyArtifactCompletionRequest
+import com.intellij.repository.search.completion.api.DependencyCompletionContributionSource.SERVER
+import com.intellij.repository.search.completion.api.DependencyCompletionEvent
+import com.intellij.repository.search.completion.api.DependencyCompletionRequest
+import com.intellij.repository.search.completion.api.DependencyCompletionResult
+import com.intellij.repository.search.completion.api.DependencyCompletionService
+import com.intellij.repository.search.completion.api.DependencyGroupCompletionRequest
+import com.intellij.repository.search.completion.api.DependencyPartCompletionResult
+import com.intellij.repository.search.completion.api.DependencyVersionCompletionRequest
+import com.intellij.testFramework.junit5.TestApplication
+import com.intellij.testFramework.junit5.TestDisposable
+import com.intellij.testFramework.replaceService
+import com.intellij.util.application
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.intellij.lang.annotations.Language
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+
+/**
+ * IDEA-392103: typing a character that occurs inside a dependency coordinate must not finish the lookup and truncate
+ * the already typed text. See [com.intellij.maven.completion.MavenCompletionCharFilter].
+ */
+@TestApplication
+class MavenDependencyCompletionCharFilterTest {
+
+ private val maven by mavenDomFixture(initialPom = MavenDomTestFixture.DEFAULT_POM)
+
+ @TestDisposable
+ private lateinit var disposable: Disposable
+
+ private val testCompletionService = object : DependencyCompletionService {
+ override fun suggestCompletions(request: DependencyCompletionRequest): Flow> =
+ flowOf(DependencyCompletionEvent.Item(DependencyCompletionResult("org.springframework", "spring-core", "6.2.0", source = SERVER)))
+
+ override fun suggestGroupCompletions(request: DependencyGroupCompletionRequest): Flow> =
+ flowOf(DependencyCompletionEvent.Item(DependencyPartCompletionResult("org.springframework", SERVER)))
+
+ override fun suggestArtifactCompletions(request: DependencyArtifactCompletionRequest): Flow> =
+ flowOf(DependencyCompletionEvent.Item(DependencyPartCompletionResult("spring-core.suffix", SERVER)))
+
+ override fun suggestVersionCompletions(request: DependencyVersionCompletionRequest): Flow> =
+ flowOf(DependencyCompletionEvent.Item(DependencyPartCompletionResult("6.2.0", SERVER)))
+ }
+
+ @BeforeEach
+ fun replaceCompletionService() {
+ application.replaceService(DependencyCompletionService::class.java, testCompletionService, disposable)
+ }
+
+ @ParameterizedTest
+ @ValueSource(chars = ['-', ':', '.', '_'])
+ fun `test typing special char in top level does not truncate typed text`(charToType: Char) = runBlocking {
+ completeAndType("""
+
+ org.springframework
+
+ """, charToType)
+
+ assertTrue(documentText.contains("org.springframework$charToType"),
+ "The typed text must be preserved and extended, but was:\n$documentText")
+ assertNotNull(activeLookup, "'$charToType' must be added to the prefix, so the lookup must stay open")
+ }
+
+ @ParameterizedTest
+ @ValueSource(chars = ['-', ':', '.', '_'])
+ fun `test typing special char in dependency tag does not truncate typed text`(charToType: Char) = runBlocking {
+ completeAndType("""
+
+ org.springframework
+
+ """, charToType)
+
+ assertTrue(documentText.contains("org.springframework$charToType"),
+ "The typed text must be preserved and extended, but was:\n$documentText")
+ assertNotNull(activeLookup, "'$charToType' must be added to the prefix, so the lookup must stay open")
+ }
+
+ @ParameterizedTest
+ @ValueSource(chars = ['-', '.', '_'])
+ fun `test typing special char in group id tag does not truncate typed text`(charToType: Char) = runBlocking {
+ completeAndType("""
+
+
+ org.springframework
+
+
+ """, charToType)
+
+ assertTrue(documentText.contains("org.springframework$charToType"),
+ "The typed text must be preserved and extended, but was:\n$documentText")
+ assertNotNull(activeLookup, "'$charToType' must be added to the prefix, so the lookup must stay open")
+ }
+
+ @ParameterizedTest
+ @ValueSource(chars = ['-', '.', '_'])
+ fun `test typing special char in artifact id tag does not truncate typed text`(charToType: Char) = runBlocking {
+ completeAndType("""
+
+
+ org.springframework
+ spring-core
+
+
+ """, charToType)
+
+ assertTrue(documentText.contains("spring-core$charToType"),
+ "The typed text must be preserved and extended, but was:\n$documentText")
+ assertNotNull(activeLookup, "'$charToType' must be added to the prefix, so the lookup must stay open")
+ }
+
+ @Test
+ fun `test typing dot in version tag keeps platform behaviour`() = runBlocking {
+ completeAndType("""
+
+
+ org.springframework
+ spring-core
+ 6.
+
+
+ """, '.')
+
+ assertTrue(documentText.contains("6.2."),
+ "The version lookup must keep completing till the typed char occurrence, but was:\n$documentText")
+ }
+
+ private suspend fun completeAndType(
+ @Language(value = "XML", prefix = "", suffix = "") dependencies: String,
+ c: Char,
+ ) {
+ maven.updateProjectPom("""
+ test
+ project
+ 1
+ ${dependencies.trimIndent()}
+ """.trimIndent())
+ maven.configTest(maven.projectPom)
+ withContext(Dispatchers.EDT) {
+ val lookupElements = maven.fixture.complete(CompletionType.BASIC)
+ assertTrue(!lookupElements.isNullOrEmpty(), "The fake completion service must have produced lookup elements")
+ assertEquals(true, (activeLookup as? LookupImpl)?.isFocused, "An explicitly invoked lookup must be focused")
+ maven.fixture.type(c)
+ }
+ }
+
+ private val activeLookup
+ get() = LookupManager.getActiveLookup(maven.fixture.editor)
+
+ private val documentText
+ get() = maven.fixture.editor.document.text
+}