From 2e09640133e9e698196cfe651c4d01bb898252ca Mon Sep 17 00:00:00 2001 From: "Kirill.Karnaukhov" Date: Tue, 24 Oct 2023 19:23:54 +0200 Subject: [PATCH] [inline-completion] ML-1632: add some tests to the platform GitOrigin-RevId: d94f8fe8e0c7d10234e9ad727e1f9c1d9932c464 --- .../completion/InlineCompletionHandler.kt | 8 +- .../session/InlineCompletionContext.kt | 9 ++ .../completion/SimpleInlineCompletionTest.kt | 100 ++++++++++++++++++ .../InlineCompletionLifecycleTestDSL.kt | 8 ++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/codeInsight/inline/completion/SimpleInlineCompletionTest.kt diff --git a/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/InlineCompletionHandler.kt b/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/InlineCompletionHandler.kt index 9188cee11ddd..4e23b9e5baa8 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/InlineCompletionHandler.kt +++ b/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/InlineCompletionHandler.kt @@ -245,7 +245,7 @@ class InlineCompletionHandler( private fun getProvider(event: InlineCompletionEvent): InlineCompletionProvider? { if (application.isUnitTestMode && testProvider != null) { - return testProvider + return testProvider?.takeIf { it.isEnabled(event) } } return InlineCompletionProvider.extensions().firstOrNull { @@ -335,6 +335,12 @@ class InlineCompletionHandler( testProvider = provider } + @TestOnly + fun registerTestHandler(provider: InlineCompletionProvider, disposable: Disposable) { + registerTestHandler(provider) + disposable.whenDisposed { unRegisterTestHandler() } + } + @TestOnly fun unRegisterTestHandler() { testProvider = null diff --git a/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/session/InlineCompletionContext.kt b/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/session/InlineCompletionContext.kt index 312f69ae9883..f7d69372bfb7 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/session/InlineCompletionContext.kt +++ b/platform/platform-impl/src/com/intellij/codeInsight/inline/completion/session/InlineCompletionContext.kt @@ -54,6 +54,15 @@ class InlineCompletionContext internal constructor(val editor: Editor, val langu isDisposed = true } + override fun toString(): String { + return if (!isDisposed) { + "InlineCompletionContext(disposed=false, textToInsert=${textToInsert()})" + } + else { + "InlineCompletionContext(disposed=true)" + } + } + private inline fun assureNotDisposed(block: () -> T): T { check(!isDisposed) { "Context is disposed. Cannot access elements." } return block() diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/inline/completion/SimpleInlineCompletionTest.kt b/platform/platform-tests/testSrc/com/intellij/codeInsight/inline/completion/SimpleInlineCompletionTest.kt new file mode 100644 index 000000000000..71d3f295d617 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/inline/completion/SimpleInlineCompletionTest.kt @@ -0,0 +1,100 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.codeInsight.inline.completion + +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionSkipTextElement +import com.intellij.openapi.fileTypes.PlainTextFileType +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +internal class SimpleInlineCompletionTest : BasePlatformTestCase() { + + private fun registerSuggestion(vararg suggestion: InlineCompletionElement) { + InlineCompletionHandler.registerTestHandler(SimpleInlineCompletionProvider(suggestion.toList()), testRootDisposable) + } + + // !!! VERY IMPORTANT !!! + override fun runInDispatchThread(): Boolean { + return false + } + + @Test + fun `test inline completion renders on typings`() = myFixture.testInlineCompletion { + init(PlainTextFileType.INSTANCE, "fu") + registerSuggestion(InlineCompletionGrayTextElement(" main(args: List) { }")) + + typeChar('n') // Type a symbol into the editor + delay() // Waiting for request to inline completion to finish + assertInlineRender(" main(args: List) { }") // Checking what is shown with inlays + insert() + assertFileContent("fun main(args: List) { }") + assertInlineHidden() + } + + @Test + fun `test inline completion does not render on direct action call`() = myFixture.testInlineCompletion { + init(PlainTextFileType.INSTANCE, "") + registerSuggestion(InlineCompletionGrayTextElement("This is tutorial")) + callInlineCompletion() // Direct action call + delay() + assertInlineHidden() + } + + @Test + fun `test inline completion renders skip elements`() = myFixture.testInlineCompletion { + init(PlainTextFileType.INSTANCE, ")") + registerSuggestion( + InlineCompletionGrayTextElement("1"), + InlineCompletionSkipTextElement(")"), + InlineCompletionGrayTextElement(" + (2)") + ) + typeChar('(') + assertFileContent("()") + delay() + + assertInlineElements { + gray("1") + skip(")") + gray(" + (2)") + } + insert() + assertFileContent("(1) + (2)") + assertInlineHidden() + } + + @Test + fun `test inline completion simple over typing`() = myFixture.testInlineCompletion { + init(PlainTextFileType.INSTANCE, "") + registerSuggestion(InlineCompletionGrayTextElement("his is tutorial")) + typeChar('T') + delay() + assertInlineRender("his is tutorial") + typeChar('h') // No 'delay' as this update is called instantly + assertInlineRender("is is tutorial") + typeChar('i') + assertInlineRender("s is tutorial") + + assertFileContent("Thi") + insert() + assertFileContent("This is tutorial") + assertInlineHidden() + } + + private class SimpleInlineCompletionProvider(val suggestion: List) : InlineCompletionProvider { + override val id: InlineCompletionProviderID = InlineCompletionProviderID("SimpleInlineCompletionProvider") + + override suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSuggestion { + return InlineCompletionSuggestion.withFlow { + suggestion.forEach { emit(it) } + } + } + + override fun isEnabled(event: InlineCompletionEvent): Boolean { + return event is InlineCompletionEvent.DocumentChange + } + } +} diff --git a/platform/testFramework/src/com/intellij/codeInsight/inline/completion/InlineCompletionLifecycleTestDSL.kt b/platform/testFramework/src/com/intellij/codeInsight/inline/completion/InlineCompletionLifecycleTestDSL.kt index 170b9271485f..b943ec0bf7aa 100644 --- a/platform/testFramework/src/com/intellij/codeInsight/inline/completion/InlineCompletionLifecycleTestDSL.kt +++ b/platform/testFramework/src/com/intellij/codeInsight/inline/completion/InlineCompletionLifecycleTestDSL.kt @@ -343,6 +343,14 @@ class InlineCompletionLifecycleTestDSL(val fixture: CodeInsightTestFixture) { } } +/** + * This is **Experimental API**. + * + * If you use this DSL to write a test in JUnit3 or JUnit4 test classes, **please set `runInDispatchThread` to `false`**, otherwise, you'll + * get a deadlock. + * + * Example: [com.intellij.codeInsight.inline.completion.SimpleInlineCompletionTest] + */ @ApiStatus.Experimental fun CodeInsightTestFixture.testInlineCompletion( timeout: Duration = DEFAULT_TEST_TIMEOUT,