11package org .jetbrains .plugins .scala
22package base
33
4- import com .intellij .openapi .actionSystem .IdeActions .{
5- ACTION_EDITOR_BACKSPACE ,
6- ACTION_EDITOR_ENTER ,
7- ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB
8- }
4+ import com .intellij .openapi .actionSystem .IdeActions .{ACTION_EDITOR_BACKSPACE , ACTION_EDITOR_ENTER , ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB }
95import com .intellij .openapi .application .ApplicationManager
106import com .intellij .openapi .application .impl .NonBlockingReadActionImpl
117import com .intellij .openapi .editor .CaretState
@@ -32,8 +28,8 @@ import scala.util.control.NonFatal
3228@ Category (Array (classOf [EditorTests ]))
3329abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase with ShortCaretMarker {
3430
35- protected val q : String = " \" "
36- protected val qq : String = " \"\" "
31+ protected val q : String = " \" "
32+ protected val qq : String = " \"\" "
3733 protected val qqq : String = " \"\"\" "
3834
3935 private implicit def p : Project = getProject
@@ -42,7 +38,9 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
4238
4339 protected def defaultFileName : String = s " aaa. ${fileType.getDefaultExtension}"
4440
45- protected def configureByText (text : String , fileName : String = defaultFileName, trimText : Boolean = false ): Unit = {
41+ protected def configureByText (text : String ,
42+ fileName : String = defaultFileName,
43+ trimText : Boolean = false ): Unit = {
4644 val (textActual, caretOffsets) = findCaretOffsets(text, trimText)
4745
4846 assertTrue(" expected at least one caret" , caretOffsets.nonEmpty)
@@ -60,26 +58,22 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
6058 }
6159 val editor = myFixture.getEditor
6260 editor.getCaretModel.moveToOffset(caretOffsets.head)
63- val caretStates = caretOffsets.map( offset => new CaretState (editor.offsetToLogicalPosition(offset), null , null ))
61+ val caretStates = caretOffsets.map { offset => new CaretState (editor.offsetToLogicalPosition(offset), null , null ) }
6462 editor.getCaretModel.setCaretsAndSelections(caretStates.asJava)
6563 }
6664
6765 /**
68- * @param textBefore
69- * editor text with caret markers before the action
70- * @param textAfter
71- * editor text with caret markers after the action
72- * @param stripTrailingSpacesAfterAction
73- * whether to trim trailing editor spaces after action perform
74- * @param testBody
75- * action to perform with `textBefore`
66+ * @param textBefore editor text with caret markers before the action
67+ * @param textAfter editor text with caret markers after the action
68+ * @param stripTrailingSpacesAfterAction whether to trim trailing editor spaces after action perform
69+ * @param testBody action to perform with `textBefore`
7670 */
7771 protected def performTest (
7872 textBefore : String ,
7973 textAfter : String ,
8074 fileName : String = defaultFileName,
8175 trimTestDataText : Boolean = false ,
82- stripTrailingSpacesAfterAction : Boolean = false
76+ stripTrailingSpacesAfterAction : Boolean = false ,
8377 )(testBody : () => Unit ): Unit = try {
8478 configureByText(textBefore, fileName, trimTestDataText)
8579
@@ -88,9 +82,10 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
8882 val (expectedText, expectedCarets) = findCaretOffsets(textAfter, trimTestDataText)
8983
9084 /**
91- * Copied from `com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl.checkResult` Replaced inner
92- * `checkResult` call with `checkCaretOffsets` It allows to see caret positions together with file text directly in
93- * the diff view of failed test It's more convenient then operating with caret offset (as simple integer value)
85+ * Copied from `com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl.checkResult`
86+ * Replaced inner `checkResult` call with `checkCaretOffsets`
87+ * It allows to see caret positions together with file text directly in the diff view of failed test
88+ * It's more convenient then operating with caret offset (as simple integer value)
9489 */
9590 Option (IdeaTestExecutionPolicy .current).foreach(_.beforeCheckResult(getFile))
9691 inWriteCommandAction {
@@ -101,6 +96,7 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
10196 }
10297 } catch {
10398 case cf : org.junit.ComparisonFailure =>
99+ printComparisonFailureRepro(textBefore, cf.getExpected, cf.getActual)
104100 throw cf.withBeforePrefix(textBefore)
105101
106102 case NonFatal (other) =>
@@ -114,25 +110,52 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
114110 protected def performTypingAction (charTyped : Char ): Unit =
115111 myFixture.`type`(charTyped)
116112
113+ private def printComparisonFailureRepro (textBefore : String , expectedAfter : String , actualAfter : String ): Unit =
114+ System .err.println(
115+ s """ <<<Repro>>>
116+ |<<<Before>>>
117+ | $textBefore
118+ |----------------------------------------------------
119+ |<<<ExpectedAfter>>>
120+ | $expectedAfter
121+ |----------------------------------------------------
122+ |<<<ActualAfter>>>
123+ | $actualAfter
124+ | """ .stripMargin
125+ )
126+
117127 protected def performTypingAction (text : String ): Unit =
118128 myFixture.`type`(text)
119129
120- protected def checkGeneratedTextAfterTyping (
121- textBefore : String ,
122- textAfter : String ,
123- charTyped : Char ,
124- fileName : String = defaultFileName
125- ): Unit =
130+ /**
131+ * Types text character-by-character and commits the editor document after each character.
132+ *
133+ * Why we need this helper for some typing tests:
134+ * - `TypedHandlerDelegate` API does not guarantee PSI is in sync with the document during typing callbacks.
135+ * - `EditorTestFixture.type(String)` types in a tight loop (`type(char)` per character) without explicit commit between characters.
136+ * - Psi commits are asynchronous and coalesced, so in tests PSI can lag behind typed text longer than in production.
137+ *
138+ * In production, human typing cadence usually gives the async commit pipeline enough time to catch up between key presses.
139+ * In tests, typing whole strings too fast can make `file.findElementAt(...)` return `null` in typed handlers and produce
140+ * behavior different from what users see in the IDE.
141+ *
142+ * Without extra "commit" some tests might fail after fixes in the formatter related to SCL-25190
143+ */
144+ protected def performTypingActionAndCommitEachChar (text : String ): Unit =
145+ text.foreach { char : Char =>
146+ performTypingAction(char)
147+
148+ getEditor.getDocument.commit(getProject)
149+ }
150+
151+ protected def checkGeneratedTextAfterTyping (textBefore : String , textAfter : String , charTyped : Char ,
152+ fileName : String = defaultFileName): Unit =
126153 performTest(textBefore, textAfter, fileName) { () =>
127154 performTypingAction(charTyped)
128155 }
129156
130- protected def checkGeneratedTextAfterTypingText (
131- textBefore : String ,
132- textAfter : String ,
133- textTyped : String ,
134- fileName : String = defaultFileName
135- ): Unit =
157+ protected def checkGeneratedTextAfterTypingText (textBefore : String , textAfter : String , textTyped : String ,
158+ fileName : String = defaultFileName): Unit =
136159 performTest(textBefore, textAfter, fileName) { () =>
137160 performTypingAction(textTyped)
138161 }
@@ -144,10 +167,7 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
144167 fileName : String = defaultFileName
145168 ): Unit =
146169 performTest(textBefore, textAfter, fileName) { () =>
147- textTyped.foreach { char : Char =>
148- performTypingAction(char)
149- getEditor.getDocument.commit(getProject)
150- }
170+ performTypingActionAndCommitEachChar(textTyped)
151171 }
152172
153173 protected def performBackspaceAction (): Unit =
@@ -222,13 +242,12 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
222242 .foldLeft(text)(_.patch(_, CARET , 0 ))
223243
224244 val expected0 = patchTextWithCarets(expectedText, expectedCarets)
225- val expected = if (stripTrailingSpaces) doStripTrailingSpaces(expected0) else expected0
245+ val expected = if (stripTrailingSpaces) doStripTrailingSpaces(expected0) else expected0
226246
227- val actual =
228- if (expectedCarets.nonEmpty)
229- patchTextWithCarets(actualText, actualCarets)
230- else
231- actualText // if expected text doesn't contain any carets, just don't assert carets positions then
247+ val actual = if (expectedCarets.nonEmpty)
248+ patchTextWithCarets(actualText, actualCarets)
249+ else
250+ actualText // if expected text doesn't contain any carets, just don't assert carets positions then
232251 assertEquals(expected, actual)
233252 }
234253}
0 commit comments