[IFT] Exclude testGuiFramework dependency from IFT modules

Also make test script to wait editor analyzed

IDEA-CR-70569

GitOrigin-RevId: f7b3f69043de4fd064aa1c4c95c25622766825a1
This commit is contained in:
Alexey Merkulov
2021-02-19 11:36:28 +00:00
committed by intellij-monorepo-bot
parent a088f477b2
commit 4cbe1d21b1
38 changed files with 771 additions and 242 deletions
@@ -16,6 +16,6 @@
<orderEntry type="module" module-name="intellij.featuresTrainer" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.testGuiFramework" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
</component>
</module>
</module>
@@ -10,8 +10,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.impl.content.BaseLabel
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.InplaceButton
import com.intellij.ui.UIBundle
import training.dsl.*
@@ -43,7 +41,7 @@ class JavaInheritanceHierarchyLesson
test {
Thread.sleep(1000)
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
@@ -3,9 +3,6 @@ package com.intellij.java.ift.lesson.navigation
import com.intellij.find.SearchTextArea
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.actionButton
import com.intellij.testGuiFramework.util.Key
import com.intellij.usageView.UsageViewBundle
import training.dsl.LessonContext
import training.dsl.LessonUtil
@@ -95,7 +92,7 @@ class JavaOccurrencesLesson
stateCheck {
editor.headerComponent == null
}
test { GuiTestUtil.shortcut(Key.ESCAPE) }
test { invokeActionViaShortcut("ESCAPE") }
}
actionTask("FindNext") {
JavaLessonsBundle.message("java.find.occurrences.find.next.in.editor", action(it))
@@ -4,11 +4,9 @@ package com.intellij.java.ift.lesson.refactorings
import com.intellij.java.refactoring.JavaRefactoringBundle
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.extractMethod.ExtractMethodHandler
import com.intellij.testGuiFramework.impl.button
import com.intellij.ui.UIBundle
import training.dsl.LessonContext
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.TaskTestContext
import training.dsl.dropMnemonic
import training.dsl.parseLessonSample
import training.learn.LessonsBundle
@@ -46,10 +44,8 @@ class JavaExtractMethodCocktailSortLesson
}
test {
with(TaskTestContext.guiTestCase) {
dialog(RefactoringBundle.message("extract.method.title"), needToKeepDialog=true) {
button(refactorButtonText).click()
}
dialog(RefactoringBundle.message("extract.method.title")) {
button(refactorButtonText).click()
}
}
}
@@ -63,10 +59,8 @@ class JavaExtractMethodCocktailSortLesson
}
test {
with(TaskTestContext.guiTestCase) {
dialog(processDuplicatesTitle) {
button(replaceButtonText).click()
}
dialog(processDuplicatesTitle) {
button(replaceButtonText).click()
}
}
}
@@ -4,8 +4,6 @@ package com.intellij.java.ift.lesson.refactorings
import com.intellij.CommonBundle
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.util.ui.UIUtil
import training.dsl.LessonContext
import training.dsl.LessonUtil
@@ -88,8 +86,8 @@ class JavaRefactoringMenuLesson : RefactoringMenuLessonBase("java.refactoring.me
stateCheck {
!extractConstantDialogShowing()
}
test {
GuiTestUtil.shortcut(Key.ENTER)
test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
}
}
}
@@ -6,7 +6,6 @@ import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.java.refactoring.JavaRefactoringBundle
import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.testGuiFramework.impl.button
import com.intellij.util.ui.UIUtil
import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -4,13 +4,15 @@ package com.intellij.java.ift.lesson.run
import com.intellij.icons.AllIcons
import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.testGuiFramework.impl.button
import training.dsl.LessonContext
import training.dsl.TaskTestContext
import training.dsl.highlightButtonById
import training.learn.lesson.general.run.CommonDebugLesson
class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") {
override val testScriptProperties = TaskTestContext.TestScriptProperties(duration = 30)
private val demoClassName = JavaRunLessonsUtils.demoClassName
override val configurationName: String = demoClassName
override val sample = JavaRunLessonsUtils.demoSample
@@ -41,11 +43,9 @@ class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") {
!inHotSwapDialog()
}
proposeModificationRestore(afterFixText)
test {
with(TaskTestContext.guiTestCase) {
dialog(null, needToKeepDialog = true) {
button("Yes").click()
}
test(waitEditorToBeReady = false) {
dialog(null) {
button("Reload").click()
}
}
}
@@ -64,8 +64,5 @@ class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") {
}
}
override val testScriptProperties: TaskTestContext.TestScriptProperties
get() = TaskTestContext.TestScriptProperties(duration = 20)
override val fileName: String = "$demoClassName.java"
}
@@ -12,15 +12,14 @@
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="library" scope="TEST" name="kotlin-test" level="project" />
<orderEntry type="library" name="Guava" level="project" />
<orderEntry type="library" name="fest" level="project" />
<orderEntry type="library" name="fest-swing" level="project" />
<orderEntry type="library" exported="" name="fest" level="project" />
<orderEntry type="library" exported="" name="fest-swing" level="project" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.diff.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.debugger.impl" />
<orderEntry type="module" module-name="intellij.platform.testGuiFramework" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.tasks.impl" />
@@ -102,7 +102,7 @@ abstract class TaskContext : LearningDslBase {
open fun addStep(step: CompletableFuture<Boolean>) = Unit
/** [action] What should be done to pass the current task */
open fun test(action: TaskTestContext.() -> Unit) = Unit
open fun test(waitEditorToBeReady: Boolean = true, action: TaskTestContext.() -> Unit) = Unit
fun triggerByFoundPathAndHighlight(highlightBorder: Boolean = true, highlightInside: Boolean = false, usePulsation: Boolean = false,
checkPath: TaskRuntimeContext.(tree: JTree, path: TreePath) -> Boolean) {
@@ -1,35 +1,58 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.dsl
import com.intellij.ide.util.treeView.NodeRenderer
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.testGuiFramework.cellReader.ExtendedJListCellReader
import com.intellij.testGuiFramework.fixtures.ComponentFixture
import com.intellij.testGuiFramework.fixtures.IdeFrameFixture
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.framework.Timeouts
import com.intellij.testGuiFramework.impl.GuiTestCase
import com.intellij.testGuiFramework.impl.findComponentWithTimeout
import com.intellij.testGuiFramework.impl.waitUntilFound
import com.intellij.testGuiFramework.util.step
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.wm.impl.IdeFrameImpl
import com.intellij.ui.KeyStrokeAdapter
import com.intellij.ui.MultilineTreeCellRenderer
import com.intellij.ui.SimpleColoredComponent
import org.fest.swing.core.GenericTypeMatcher
import org.fest.swing.core.Robot
import org.fest.swing.driver.BasicJListCellReader
import org.fest.swing.driver.ComponentDriver
import org.fest.swing.exception.ComponentLookupException
import org.fest.swing.exception.WaitTimedOutError
import org.fest.swing.fixture.AbstractComponentFixture
import org.fest.swing.fixture.ContainerFixture
import org.fest.swing.fixture.JButtonFixture
import org.fest.swing.fixture.JListFixture
import org.fest.swing.timing.Condition
import org.fest.swing.timing.Pause
import org.fest.swing.timing.Timeout
import training.ui.LearningUiUtil
import training.ui.LearningUiUtil.findComponentWithTimeout
import training.util.invokeActionForFocusContext
import java.awt.Component
import java.awt.Container
import java.util.*
import java.util.concurrent.TimeUnit
import javax.swing.JButton
import javax.swing.JDialog
import javax.swing.JLabel
import javax.swing.JList
import kotlin.collections.ArrayList
@LearningDsl
class TaskTestContext(rt: TaskRuntimeContext): TaskRuntimeContext(rt) {
private val defaultTimeout = Timeout.timeout(3, TimeUnit.SECONDS)
val robot: Robot get() = LearningUiUtil.robot
data class TestScriptProperties (
val duration: Int = 6, //seconds
val duration: Int = 15, //seconds
val skipTesting: Boolean = false
)
fun type(text: String) {
GuiTestUtil.typeText(text)
robot.waitForIdle()
for (element in text) {
robot.type(element)
Pause.pause(10, TimeUnit.MILLISECONDS)
}
Pause.pause(300, TimeUnit.MILLISECONDS)
}
fun actions(vararg actionIds: String) {
@@ -39,50 +62,274 @@ class TaskTestContext(rt: TaskRuntimeContext): TaskRuntimeContext(rt) {
}
}
fun ideFrame(action: IdeFrameFixture.() -> Unit) {
with(guiTestCase) {
ideFrame {
// Note: It is not recursive call here. It is GuiTestCase#ideFrame
action()
}
fun ideFrame(action: ContainerFixture<IdeFrameImpl>.() -> Unit) {
with(findIdeFrame(robot, defaultTimeout)) {
action()
}
}
fun <ComponentType : Component> waitComponent(componentClass: Class<ComponentType>, partOfName: String? = null) {
waitUntilFound(null, componentClass, Timeouts.seconds02) {
(if (partOfName != null) it.javaClass.name.contains(partOfName) else true) && it.isShowing
LearningUiUtil.waitUntilFound(robot, null, typeMatcher(componentClass) {
(if (partOfName != null) it.javaClass.name.contains(partOfName)
else true) && it.isShowing
}, defaultTimeout)
}
/**
* Finds a JList component in hierarchy of context component with a containingItem and returns JListFixture.
*
* @throws ComponentLookupException if component has not been found or timeout exceeded
*/
fun <C : Container> ContainerFixture<C>.jList(containingItem: String? = null, timeout: Timeout = defaultTimeout): JListFixture {
return generalListFinder(timeout, containingItem) { element, p -> element == p }
}
/**
* Finds a JButton component in hierarchy of context component with a name and returns ExtendedButtonFixture.
*
* @throws ComponentLookupException if component has not been found or timeout exceeded
*/
fun <C : Container> ContainerFixture<C>.button(name: String, timeout: Timeout = defaultTimeout): JButtonFixture {
val jButton: JButton = findComponentWithTimeout(timeout) { it.isShowing && it.isVisible && it.text == name }
return JButtonFixture(robot(), jButton)
}
fun <C : Container> ContainerFixture<C>.actionButton(actionName: String, timeout: Timeout = defaultTimeout)
: AbstractComponentFixture<*, ActionButton, ComponentDriver<*>> {
val actionButton: ActionButton = findComponentWithTimeout(timeout) {
it.isShowing && it.isEnabled && actionName == it.action.templatePresentation.text
}
return ActionButtonFixture(robot(), actionButton)
}
// Modified copy-paste
fun <C : Container> ContainerFixture<C>.jListContains(partOfItem: String? = null, timeout: Timeout = Timeouts.seconds02): JListFixture {
return step("search '$partOfItem' in list") {
val extCellReader = ExtendedJListCellReader()
val myJList: JList<*> = findComponentWithTimeout(timeout) { jList: JList<*> ->
if (partOfItem == null) true //if were searching for any jList()
else {
val elements = (0 until jList.model.size).map { extCellReader.valueAt(jList, it) }
elements.any { it.toString().contains(partOfItem) } && jList.isShowing
}
}
val jListFixture = JListFixture(robot(), myJList)
jListFixture.replaceCellReader(extCellReader)
return@step jListFixture
}
fun <C : Container> ContainerFixture<C>.jListContains(partOfItem: String? = null, timeout: Timeout = defaultTimeout): JListFixture {
return generalListFinder(timeout, partOfItem) { element, p -> element.contains(p) }
}
/**
* Finds JDialog with a specific title (if title is null showing dialog should be only one) and returns created JDialogFixture
*/
fun dialog(title: String? = null,
ignoreCaseTitle: Boolean = false,
predicate: (String, String) -> Boolean = { found: String, wanted: String -> found == wanted },
timeout: Timeout = defaultTimeout,
func: ContainerFixture<JDialog>.() -> Unit = {})
: AbstractComponentFixture<*, JDialog, ComponentDriver<*>> {
val jDialogFixture = if (title == null) {
val jDialog = LearningUiUtil.waitUntilFound(robot, null, typeMatcher(JDialog::class.java) { true }, timeout)
JDialogFixture(robot, jDialog)
}
else {
try {
val dialog = withPauseWhenNull(timeout = timeout) {
val allMatchedDialogs = robot.finder().findAll(typeMatcher(JDialog::class.java) {
it.isFocused &&
if (ignoreCaseTitle) predicate(it.title.toLowerCase(), title.toLowerCase()) else predicate(it.title, title)
}).filter { it.isShowing && it.isEnabled && it.isVisible }
if (allMatchedDialogs.size > 1) throw Exception(
"Found more than one (${allMatchedDialogs.size}) dialogs matched title \"$title\"")
allMatchedDialogs.firstOrNull()
}
JDialogFixture(robot, dialog)
}
catch (timeoutError: WaitTimedOutError) {
throw ComponentLookupException("Timeout error for finding JDialog by title \"$title\" for ${timeout.duration()}")
}
}
func(jDialogFixture)
return jDialogFixture
}
class SimpleComponentFixture(robot: Robot, target: Component): ComponentFixture<SimpleComponentFixture, Component>(
SimpleComponentFixture::class.java, robot, target)
fun IdeFrameFixture.jComponent(target: Component): SimpleComponentFixture {
fun ContainerFixture<*>.jComponent(target: Component): AbstractComponentFixture<*, Component, ComponentDriver<*>> {
return SimpleComponentFixture(robot(), target)
}
fun invokeActionViaShortcut(shortcut: String) {
val keyStroke = KeyStrokeAdapter.getKeyStroke(shortcut)
robot.pressAndReleaseKey(keyStroke.keyCode, keyStroke.modifiers)
}
companion object {
@Volatile
var inTestMode: Boolean = false
}
val guiTestCase: GuiTestCase by lazy { GuiTestCase() }
////////////------------------------------
private fun <C : Container> ContainerFixture<C>.generalListFinder(timeout: Timeout,
containingItem: String?,
predicate: (String, String) -> Boolean): JListFixture {
val extCellReader = ExtendedJListCellReader()
val myJList: JList<*> = findComponentWithTimeout(timeout) { jList: JList<*> ->
if (containingItem == null) true //if were searching for any jList()
else {
val elements = (0 until jList.model.size).mapNotNull { extCellReader.valueAt(jList, it) }
elements.any { predicate(it, containingItem) } && jList.isShowing
}
}
val jListFixture = JListFixture(robot(), myJList)
jListFixture.replaceCellReader(extCellReader)
return jListFixture
}
/**
* waits for [timeout] when functionProbeToNull() not return null
*
* @throws WaitTimedOutError with the text: "Timed out waiting for $timeout second(s) until {@code conditionText} will be not null"
*/
private fun <ReturnType> withPauseWhenNull(conditionText: String = "function to probe",
timeout: Timeout = defaultTimeout,
functionProbeToNull: () -> ReturnType?): ReturnType {
var result: ReturnType? = null
waitUntil("$conditionText will be not null", timeout) {
result = functionProbeToNull()
result != null
}
return result!!
}
private fun waitUntil(condition: String, timeout: Timeout = defaultTimeout, conditionalFunction: () -> Boolean) {
Pause.pause(object : Condition("${timeout.duration()} until $condition") {
override fun test() = conditionalFunction()
}, timeout)
}
private class SimpleComponentFixture(robot: Robot, target: Component) :
ComponentFixture<SimpleComponentFixture, Component>(SimpleComponentFixture::class.java, robot, target)
private fun <ComponentType : Component?> typeMatcher(componentTypeClass: Class<ComponentType>,
matcher: (ComponentType) -> Boolean): GenericTypeMatcher<ComponentType> {
return object : GenericTypeMatcher<ComponentType>(componentTypeClass) {
override fun isMatching(component: ComponentType): Boolean = matcher(component)
}
}
}
private fun getValueWithCellRenderer(cellRendererComponent: Component, isExtended: Boolean = true): String? {
val result = when (cellRendererComponent) {
is JLabel -> cellRendererComponent.text
is NodeRenderer -> {
if (isExtended) cellRendererComponent.getFullText()
else cellRendererComponent.getFirstText()
} //should stands before SimpleColoredComponent because it is more specific
is SimpleColoredComponent -> cellRendererComponent.getFullText()
is MultilineTreeCellRenderer -> cellRendererComponent.text
else -> cellRendererComponent.findText()
}
return result?.trimEnd()
}
private class ExtendedJListCellReader : BasicJListCellReader() {
override fun valueAt(list: JList<*>, index: Int): String? {
val element = list.model.getElementAt(index) ?: return null
@Suppress("UNCHECKED_CAST")
val cellRendererComponent = (list as JList<Any>).cellRenderer
.getListCellRendererComponent(list, element, index, true, true)
return getValueWithCellRenderer(cellRendererComponent)
}
}
private fun SimpleColoredComponent.getFullText(): String {
return invokeAndWaitIfNeeded {
this.getCharSequence(false).toString()
}
}
private fun SimpleColoredComponent.getFirstText(): String {
return invokeAndWaitIfNeeded {
this.getCharSequence(true).toString()
}
}
private fun Component.findText(): String? {
try {
assert(this is Container)
val container = this as Container
val resultList = ArrayList<String>()
resultList.addAll(
findAllWithBFS(container, JLabel::class.java)
.asSequence()
.filter { !it.text.isNullOrEmpty() }
.map { it.text }
.toList()
)
resultList.addAll(
findAllWithBFS(container, SimpleColoredComponent::class.java)
.asSequence()
.filter {
it.getFullText().isNotEmpty()
}
.map {
it.getFullText()
}
.toList()
)
return resultList.firstOrNull { it.isNotEmpty() }
}
catch (ignored: ComponentLookupException) {
return null
}
}
private fun <ComponentType : Component> findAllWithBFS(container: Container, clazz: Class<ComponentType>): List<ComponentType> {
val result = LinkedList<ComponentType>()
val queue: Queue<Component> = LinkedList()
@Suppress("UNCHECKED_CAST")
fun check(container: Component) {
if (clazz.isInstance(container)) result.add(container as ComponentType)
}
queue.add(container)
while (queue.isNotEmpty()) {
val polled = queue.poll()
check(polled)
if (polled is Container)
queue.addAll(polled.components)
}
return result
}
private open class ComponentFixture<S, C : Component>(selfType: Class<S>, robot: Robot, target: C)
: AbstractComponentFixture<S, C, ComponentDriver<*>>(selfType, robot, target) {
override fun createDriver(robot: Robot): ComponentDriver<*> {
return ComponentDriver<Component>(robot)
}
}
private class ActionButtonFixture(robot: Robot, target: ActionButton):
ComponentFixture<ActionButtonFixture, ActionButton>(ActionButtonFixture::class.java, robot, target), ContainerFixture<ActionButton>
private class IdeFrameFixture(robot: Robot, target: IdeFrameImpl)
: ComponentFixture<IdeFrameFixture, IdeFrameImpl>(IdeFrameFixture::class.java, robot, target), ContainerFixture<IdeFrameImpl>
private class JDialogFixture(robot: Robot, jDialog: JDialog) :
ComponentFixture<JDialogFixture, JDialog>(JDialogFixture::class.java, robot, jDialog), ContainerFixture<JDialog>
private fun findIdeFrame(robot: Robot, timeout: Timeout): IdeFrameFixture {
val matcher: GenericTypeMatcher<IdeFrameImpl> = object : GenericTypeMatcher<IdeFrameImpl>(
IdeFrameImpl::class.java) {
override fun isMatching(frame: IdeFrameImpl): Boolean {
return true
}
}
return try {
Pause.pause(object : Condition("IdeFrame to show up") {
override fun test(): Boolean {
return !robot.finder().findAll(matcher).isEmpty()
}
}, timeout)
val ideFrame = robot.finder().find(matcher)
IdeFrameFixture(robot, ideFrame)
}
catch (timedOutError: WaitTimedOutError) {
throw ComponentLookupException("Unable to find IdeFrame in " + timeout.duration())
}
}
@@ -309,7 +309,7 @@ class LessonExecutor(val lesson: KLesson, val project: Project, initialEditor: E
}
private fun processTestActions(taskContext: TaskContextImpl) {
if (TaskTestContext.inTestMode) {
if (TaskTestContext.inTestMode && taskContext.testActions.isNotEmpty()) {
LessonManager.instance.testActionsExecutor.execute {
taskContext.testActions.forEach { it.run() }
}
@@ -14,7 +14,6 @@ import com.intellij.util.Alarm
import training.dsl.LearningBalloonConfig
import training.dsl.TaskContext
import training.dsl.TaskRuntimeContext
import training.dsl.TaskTestContext
import training.learn.ActionsRecorder
import training.learn.LearnBundle
import training.learn.lesson.LessonManager
@@ -189,8 +188,6 @@ private class ExtractTaskPropertiesContext(override val project: Project) : Task
hasDetection = true
}
override fun test(action: TaskTestContext.() -> Unit) = Unit // do nothing
override fun action(actionId: String): String = "" //Doesn't matter what to return
override fun code(sourceSample: String): String = "" //Doesn't matter what to return
@@ -1,10 +1,8 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.dsl.impl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.invokeLater
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.openapi.application.*
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
@@ -222,9 +220,21 @@ internal class TaskContextImpl(private val lessonExecutor: LessonExecutor,
steps.add(step)
}
override fun test(action: TaskTestContext.() -> Unit) {
override fun test(waitEditorToBeReady: Boolean, action: TaskTestContext.() -> Unit) {
testActions.add(Runnable {
DumbService.getInstance(runtimeContext.project).waitForSmartMode()
// This wait implementation is quite ugly, but it works and it is needed in the test mode only. So should be ok for now.
if (waitEditorToBeReady) {
val psiFile = invokeAndWaitIfNeeded { PsiDocumentManager.getInstance(project).getPsiFile(runtimeContext.editor.document) } ?: return@Runnable
var t = 0
val step = 100
while (!runReadAction { DaemonCodeAnalyzerEx.getInstanceEx(project).isErrorAnalyzingFinished(psiFile) }) {
Thread.sleep(step.toLong())
t += step
if (t > 3000) return@Runnable
}
}
TaskTestContext(runtimeContext).action()
})
}
@@ -9,14 +9,13 @@ import com.intellij.openapi.editor.actions.ToggleShowLineNumbersGloballyAction
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.util.SystemInfo
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.jList
import com.intellij.testGuiFramework.util.Key
import com.intellij.util.ui.UIUtil
import org.fest.swing.driver.ComponentDriver
import training.dsl.*
import training.learn.LearnBundle
import training.learn.LessonsBundle
import training.learn.course.KLesson
import java.awt.Component
import java.awt.event.KeyEvent
import javax.swing.JPanel
@@ -64,8 +63,8 @@ class GotoActionLesson(private val sample: LessonSample, private val firstLesson
// Note 1: it is editor from test IDE fixture
// Note 2: In order to pass this task without interference with later task I need to firstly focus lesson
// and only then press Escape
editor.requestFocus()
GuiTestUtil.shortcut(Key.ESCAPE)
ComponentDriver<Component>(robot).focusAndWaitForFocusGain(editor.contentComponent)
invokeActionViaShortcut("ESCAPE")
}
}
}
@@ -4,7 +4,6 @@ package training.learn.lesson.general.assistance
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.testGuiFramework.impl.button
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -59,7 +58,7 @@ class CodeFormatLesson(private val sample: LessonSample, private val optimizeImp
restoreAfterStateBecomeFalse {
focusOwner is EditorComponentImpl
}
test {
test(waitEditorToBeReady = false) {
properties.setValue("LayoutCode.optimizeImports", true)
ideFrame {
button("Run").click()
@@ -2,11 +2,6 @@
package training.learn.lesson.general.assistance
import com.intellij.ide.IdeBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.jList
import com.intellij.testGuiFramework.util.Key
import com.intellij.testGuiFramework.util.Modifier
import com.intellij.testGuiFramework.util.Shortcut
import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.learn.LessonsBundle
@@ -59,9 +54,8 @@ abstract class EditorCodingAssistanceLesson(private val sample: LessonSample) :
restoreIfModifiedOrMoved()
test {
Thread.sleep(500)
val errorDescriptionShortcut = Shortcut(hashSetOf(Modifier.CONTROL), Key.F1)
GuiTestUtil.shortcut(errorDescriptionShortcut)
GuiTestUtil.shortcut(errorDescriptionShortcut)
invokeActionViaShortcut("CONTROL F1")
invokeActionViaShortcut("CONTROL F1")
}
}
@@ -4,8 +4,6 @@ package training.learn.lesson.general.assistance
import com.intellij.codeInsight.documentation.DocumentationComponent
import com.intellij.codeInsight.documentation.QuickDocUtil
import com.intellij.codeInsight.hint.ImplementationViewComponent
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -31,7 +29,7 @@ class QuickPopupsLesson(private val sample: LessonSample) :
stateCheck { checkDocComponentClosed() }
restoreIfModifiedOrMoved()
test {
GuiTestUtil.shortcut(Key.ESCAPE)
invokeActionViaShortcut("ESCAPE")
}
}
@@ -1,8 +1,6 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.learn.lesson.general.completion
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import training.dsl.LessonContext
import training.dsl.LessonSample
import training.dsl.LessonUtil
@@ -46,7 +44,7 @@ abstract class BasicCompletionLessonBase : KLesson("Basic completion", LessonsBu
}
}
test {
GuiTestUtil.typeText(item1StartToType)
type(item1StartToType)
}
}
task("EditorChooseLookupItem") {
@@ -59,8 +57,8 @@ abstract class BasicCompletionLessonBase : KLesson("Basic completion", LessonsBu
!isTheFirstVariant(ui)
} ?: true
}
test {
GuiTestUtil.shortcut(Key.ENTER)
test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
}
}
waitBeforeContinue(500)
@@ -8,8 +8,6 @@ import com.intellij.openapi.wm.impl.content.BaseLabel
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.testGuiFramework.framework.GuiTestUtil.shortcut
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.UIBundle
import com.intellij.ui.table.JBTable
import training.dsl.LessonContext
@@ -56,7 +54,7 @@ abstract class DeclarationAndUsagesLesson
actions(it)
ideFrame {
waitComponent(JBTable::class.java, "ShowUsagesTable")
shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
}
@@ -4,8 +4,6 @@ package training.learn.lesson.general.navigation
import com.intellij.ide.dnd.aware.DnDAwareTree
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.speedSearch.SpeedSearchSupply
import training.dsl.LessonContext
import training.dsl.LessonUtil
@@ -48,7 +46,7 @@ abstract class FileStructureLesson
text(LessonsBundle.message("file.structure.navigate", LessonUtil.rawEnter()))
stateCheck { editor.caretModel.logicalPosition == methodToFindPosition }
restoreState { !checkWordInSearch(searchSubstring) }
test { GuiTestUtil.shortcut(Key.ENTER) }
test { invokeActionViaShortcut("ENTER") }
}
task("ActivateStructureToolWindow") {
text(LessonsBundle.message("file.structure.toolwindow", action(it)))
@@ -9,20 +9,16 @@ import com.intellij.find.impl.FindInProjectSettingsBase
import com.intellij.find.impl.FindPopupPanel
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.testGuiFramework.fixtures.extended.ExtendedTableFixture
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.actionButton
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.impl.findComponentWithTimeout
import com.intellij.testGuiFramework.util.Key
import com.intellij.usages.UsagePresentation
import com.intellij.util.ui.UIUtil
import org.fest.swing.core.MouseClickInfo
import org.fest.swing.data.TableCell
import org.fest.swing.fixture.JTableFixture
import org.fest.swing.fixture.JTextComponentFixture
import training.dsl.*
import training.learn.LessonsBundle
import training.learn.course.KLesson
import training.ui.LearningUiUtil.findComponentWithTimeout
import java.awt.event.InputEvent
import java.awt.event.KeyEvent
import javax.swing.*
@@ -42,7 +38,6 @@ class FindInFilesLesson(override val existedFile: String)
!popup.helper.isReplaceState
}
test {
Thread.sleep(300)
actions(it)
}
}
@@ -61,7 +56,7 @@ class FindInFilesLesson(override val existedFile: String)
LessonUtil.rawKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.ALT_DOWN_MASK))))
highlightAndTriggerWhenButtonSelected(wholeWordsButtonText)
showWarningIfPopupClosed(false)
test {
test(waitEditorToBeReady = false) {
ideFrame {
actionButton(wholeWordsButtonText).click()
}
@@ -84,9 +79,8 @@ class FindInFilesLesson(override val existedFile: String)
restoreByUi(restoreId = showPopupTaskId)
test {
ideFrame {
Thread.sleep(300)
val table = findComponentWithTimeout { table: JTable -> table.findLastRowIndexOfItemWithText(it) != -1 }
val tableFixture = ExtendedTableFixture(robot(), table)
val tableFixture = JTableFixture(robot(), table)
val rowIndex = table.findLastRowIndexOfItemWithText(it)
tableFixture.click(TableCell.row(rowIndex).column(0), MouseClickInfo.leftButton())
}
@@ -97,7 +91,7 @@ class FindInFilesLesson(override val existedFile: String)
text(LessonsBundle.message("find.in.files.go.to.file", LessonUtil.rawEnter()))
stateCheck { virtualFile.name != existedFile.substringAfterLast('/') }
restoreByUi(restoreId = showPopupTaskId)
test { GuiTestUtil.shortcut(Key.ENTER) }
test { invokeActionViaShortcut("ENTER") }
}
task("ReplaceInPath") {
@@ -157,7 +151,6 @@ class FindInFilesLesson(override val existedFile: String)
showWarningIfPopupClosed(true)
test {
ideFrame {
Thread.sleep(300)
button(replaceAllButtonText).click()
}
}
@@ -168,10 +161,9 @@ class FindInFilesLesson(override val existedFile: String)
text(LessonsBundle.message("find.in.files.confirm.replace", strong(replaceButtonText)))
stateCheck { editor.document.charsSequence.contains("orange") }
restoreByUi(delayMillis = defaultRestoreDelay)
test {
ideFrame {
Thread.sleep(300)
findMessageDialog(replaceAllDialogTitle).click(replaceButtonText)
test(waitEditorToBeReady = false) {
dialog(title = "Replace All") {
button(replaceButtonText).click()
}
}
}
@@ -7,8 +7,6 @@ import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.wm.IdeFrame
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.SearchTextField
import com.intellij.ui.components.JBList
import com.intellij.ui.components.fields.ExtendableTextField
@@ -89,7 +87,9 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreState {
!checkRecentFilesSearch("rfd") || previous.ui?.isShowing != true
}
test { GuiTestUtil.shortcut(Key.ENTER) }
test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
}
}
actionTask("RecentFiles") {
@@ -112,7 +112,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreIfRecentFilesPopupClosed()
test {
repeat(countOfFilesToDelete) {
GuiTestUtil.shortcut(Key.DELETE)
invokeActionViaShortcut("DELETE")
}
}
}
@@ -120,7 +120,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
task {
text(LessonsBundle.message("recent.files.close.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE)))
stateCheck { focusOwner is IdeFrame }
test { GuiTestUtil.shortcut(Key.ESCAPE) }
test { invokeActionViaShortcut("ESCAPE") }
}
actionTask("RecentLocations") {
@@ -146,7 +146,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreState {
!checkRecentLocationsSearch(stringForRecentFilesSearch) || previous.ui?.isShowing != true
}
test { GuiTestUtil.shortcut(Key.ENTER) }
test { invokeActionViaShortcut("ENTER") }
}
}
@@ -188,9 +188,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
private fun checkWordInSearch(expected: String, component: JComponent): Boolean {
val supply = SpeedSearchSupply.getSupply(component)
val enteredPrefix = supply?.enteredPrefix ?: return false
val equals = enteredPrefix.equals(expected, ignoreCase = true)
System.err.println("expected = '$expected', enteredPrefix = '$enteredPrefix', equals = $equals")
return equals
return enteredPrefix.equals(expected, ignoreCase = true)
}
private fun TaskContext.restoreIfRecentFilesPopupClosed() {
@@ -8,10 +8,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.ProjectScope
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.testGuiFramework.util.Modifier
import com.intellij.testGuiFramework.util.Shortcut
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.util.ui.UIUtil
import training.dsl.*
@@ -52,7 +48,7 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
!checkInsideSearchEverywhere()
}
test {
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
@@ -79,7 +75,7 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
!checkInsideSearchEverywhere() && focusOwner !is JList<*>
}
test {
GuiTestUtil.shortcut(Shortcut(HashSet(setOf(Modifier.ALT)), Key.P))
invokeActionViaShortcut("ALT P")
}
}
@@ -93,7 +89,7 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
task {
text(LessonsBundle.message("search.everywhere.close.documentation.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE)))
stateCheck { previous.ui?.isShowing != true }
test { GuiTestUtil.shortcut(Key.ESCAPE) }
test { invokeActionViaShortcut("ENTER") }
}
task {
@@ -103,8 +99,8 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
if (TaskTestContext.inTestMode) task {
stateCheck { focusOwner is EditorComponentImpl }
test {
GuiTestUtil.shortcut(Key.ESCAPE)
GuiTestUtil.shortcut(Key.ESCAPE)
invokeActionViaShortcut("ESCAPE")
invokeActionViaShortcut("ESCAPE")
}
}
@@ -3,7 +3,6 @@ package training.learn.lesson.general.refactorings
import com.intellij.CommonBundle
import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.impl.button
import com.intellij.ui.UIBundle
import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -43,11 +42,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
}
restoreByUi(delayMillis = defaultRestoreDelay)
test {
with(TaskTestContext.guiTestCase) {
dialog(extractMethodDialogTitle, needToKeepDialog = true) {
button(okButtonText).click()
}
test(waitEditorToBeReady = false) {
dialog(extractMethodDialogTitle) {
button(okButtonText).click()
}
}
}
@@ -61,11 +58,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
}
restoreByUi(restoreId = startTaskId, delayMillis = defaultRestoreDelay)
test {
with(TaskTestContext.guiTestCase) {
dialog(extractMethodDialogTitle) {
button(yesButtonText).click()
}
test(waitEditorToBeReady = false) {
dialog(extractMethodDialogTitle) {
button(yesButtonText).click()
}
}
}
@@ -76,11 +71,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
previous.ui?.isShowing?.not() ?: true
}
test {
with(TaskTestContext.guiTestCase) {
dialog(replaceFragmentDialogTitle) {
button(UIBundle.message("replace.prompt.replace.button").dropMnemonic()).click()
}
test(waitEditorToBeReady = false) {
dialog(replaceFragmentDialogTitle) {
button(UIBundle.message("replace.prompt.replace.button").dropMnemonic()).click()
}
}
}
@@ -1,7 +1,6 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.learn.lesson.general.refactorings
import com.intellij.testGuiFramework.impl.jList
import com.intellij.ui.components.JBList
import training.dsl.LessonContext
import training.dsl.LessonSample
@@ -40,9 +39,13 @@ class ExtractVariableFromBubbleLesson(private val sample: LessonSample)
}
}
actionTask("NextTemplateVariable") {
task("NextTemplateVariable") {
//TODO: fix the shortcut: it should be ${action(it)} but with preference for Enter
LessonsBundle.message("extract.variable.choose.name", LessonUtil.rawEnter())
text(LessonsBundle.message("extract.variable.choose.name", LessonUtil.rawEnter()))
trigger(it)
test(waitEditorToBeReady = false) {
actions(it)
}
}
}
}
@@ -3,8 +3,6 @@ package training.learn.lesson.general.refactorings
import com.intellij.idea.ActionsBundle
import com.intellij.refactoring.rename.inplace.InplaceRefactoring
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.components.JBList
import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -38,7 +36,7 @@ abstract class RefactoringMenuLessonBase(lessonId: String) : KLesson(lessonId, L
stateCheck { hasInplaceRename() }
restoreState(delayMillis = defaultRestoreDelay, restoreId = showPopupTaskId) { focusOwner !is JBList<*> }
test {
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
@@ -47,8 +45,8 @@ abstract class RefactoringMenuLessonBase(lessonId: String) : KLesson(lessonId, L
stateCheck {
!hasInplaceRename()
}
test {
GuiTestUtil.shortcut(Key.ENTER)
test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
}
}
}
@@ -19,8 +19,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.psi.PsiDocumentManager
import com.intellij.tasks.TaskBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.util.ui.UIUtil
import com.intellij.xdebugger.*
import com.intellij.xdebugger.impl.XDebugSessionImpl
@@ -196,7 +194,7 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
proposeModificationRestore(sample.text)
test {
Thread.sleep(500)
GuiTestUtil.shortcut(Key.ESCAPE)
invokeActionViaShortcut("ESCAPE")
}
}
}
@@ -249,8 +247,8 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
proposeModificationRestore(sample.text)
test {
Thread.sleep(500)
GuiTestUtil.shortcut(if (stepIntoDirection == "→") Key.RIGHT else Key.LEFT)
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut(if (stepIntoDirection == "→") "RIGHT" else "LEFT")
invokeActionViaShortcut("ENTER")
}
}
}
@@ -286,7 +284,7 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
else checkForBreakpoints()
}
test {
GuiTestUtil.shortcut(Key.ESCAPE)
invokeActionViaShortcut("ESCAPE")
invokeLater {
WriteCommandAction.runWriteCommandAction(project) {
val offset = sample.text.indexOf("[0]")
@@ -356,6 +354,9 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
}
task(expressionToBeEvaluated) {
before {
LearningUiHighlightingManager.clearHighlights()
}
text(LessonsBundle.message("debug.workflow.type.result", code(it),
strong(XDebuggerBundle.message("xdebugger.evaluate.label.expression"))))
stateCheck { checkWordInTextField(it) }
@@ -377,7 +378,10 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
dialog?.title == XDebuggerBundle.message("xdebugger.evaluate.dialog.title") && root?.children?.size == 1
}
proposeModificationRestore(afterFixText)
test { GuiTestUtil.shortcut(Key.ENTER) }
test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
invokeActionViaShortcut("ESCAPE")
}
}
}
@@ -5,8 +5,6 @@ import com.intellij.execution.ExecutionBundle
import com.intellij.execution.RunManager
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.impl.jList
import com.intellij.ui.UIBundle
import com.intellij.ui.components.JBCheckBox
import training.dsl.*
@@ -108,7 +106,7 @@ abstract class CommonRunConfigurationLesson(id: String) : KLesson(id, LessonsBun
stateCheck {
focusOwner is EditorComponentImpl
}
test {
test(waitEditorToBeReady = false) {
ideFrame {
button("Cancel").click()
}
@@ -0,0 +1,327 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.ui
import com.intellij.util.ConcurrencyUtil
import com.intellij.util.ui.EdtInvocationManager
import org.fest.swing.awt.AWT
import org.fest.swing.core.*
import org.fest.swing.core.Robot
import org.fest.swing.edt.GuiActionRunner
import org.fest.swing.edt.GuiQuery
import org.fest.swing.hierarchy.ComponentHierarchy
import org.fest.swing.timing.Pause
import java.awt.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import javax.swing.JComponent
import javax.swing.JPopupMenu
import javax.swing.SwingUtilities
// It is a copy-paster from testGuiFramework (with several changes)
internal class IftSmartWaitRobot : Robot {
override fun moveMouse(component: Component) {
var where = AWT.visibleCenterOf(component)
if (component is JComponent) {
Scrolling.scrollToVisible(this, component)
where = AWT.visibleCenterOf(component)
}
moveMouse(component, where)
}
override fun moveMouse(component: Component, point: Point) {
val translatedPoint = performOnEdt { AWT.translate(component, point.x, point.y) }
requireNotNull(translatedPoint) { "Translated point should be not null" }
moveMouse(translatedPoint.x, translatedPoint.y)
}
override fun moveMouse(point: Point) {
moveMouse(point.x, point.y)
}
override fun click(component: Component) {
moveMouse(component)
basicRobot.click(component)
}
override fun click(component: Component, mouseButton: MouseButton) {
moveMouse(component)
basicRobot.click(component, mouseButton)
}
override fun click(component: Component, mouseButton: MouseButton, counts: Int) {
moveMouse(component)
basicRobot.click(component, mouseButton, counts)
}
override fun click(component: Component, point: Point) {
moveMouse(component, point)
basicRobot.click(component, point)
}
override fun showWindow(window: Window) {
basicRobot.showWindow(window)
}
override fun showWindow(window: Window, dimension: Dimension) {
basicRobot.showWindow(window, dimension)
}
override fun showWindow(window: Window, dimension: Dimension?, p2: Boolean) {
basicRobot.showWindow(window, dimension, p2)
}
override fun isActive(): Boolean = basicRobot.isActive
override fun pressAndReleaseKey(p0: Int, vararg p1: Int) {
basicRobot.pressAndReleaseKey(p0, *p1)
}
override fun showPopupMenu(component: Component): JPopupMenu =
basicRobot.showPopupMenu(component)
override fun showPopupMenu(component: Component, point: Point): JPopupMenu =
basicRobot.showPopupMenu(component, point)
override fun jitter(component: Component) {
basicRobot.jitter(component)
}
override fun jitter(component: Component, point: Point) {
basicRobot.jitter(component, point)
}
override fun pressModifiers(p0: Int) {
basicRobot.pressModifiers(p0)
}
override fun pressMouse(mouseButton: MouseButton) {
basicRobot.pressMouse(mouseButton)
}
override fun pressMouse(component: Component, point: Point) {
basicRobot.pressMouse(component, point)
}
override fun pressMouse(component: Component, point: Point, mouseButton: MouseButton) {
basicRobot.pressMouse(component, point, mouseButton)
}
override fun pressMouse(point: Point, mouseButton: MouseButton) {
basicRobot.pressMouse(point, mouseButton)
}
override fun hierarchy(): ComponentHierarchy =
basicRobot.hierarchy()
override fun releaseKey(p0: Int) {
basicRobot.releaseKey(p0)
}
override fun isDragging(): Boolean = basicRobot.isDragging
override fun printer(): ComponentPrinter = basicRobot.printer()
override fun type(char: Char) {
basicRobot.type(char)
}
override fun type(char: Char, component: Component) {
basicRobot.type(char, component)
}
override fun requireNoJOptionPaneIsShowing() {
basicRobot.requireNoJOptionPaneIsShowing()
}
override fun cleanUp() {
basicRobot.cleanUp()
}
override fun releaseMouse(mouseButton: MouseButton) {
basicRobot.releaseMouse(mouseButton)
}
override fun pressKey(p0: Int) {
basicRobot.pressKey(p0)
}
override fun settings(): Settings = basicRobot.settings()
override fun enterText(text: String) {
basicRobot.enterText(text)
}
override fun enterText(text: String, component: Component) {
basicRobot.enterText(text, component)
}
override fun releaseMouseButtons() {
basicRobot.releaseMouseButtons()
}
override fun rightClick(component: Component) {
basicRobot.rightClick(component)
}
override fun focus(component: Component) {
basicRobot.focus(component)
}
override fun doubleClick(component: Component) {
basicRobot.doubleClick(component)
}
override fun cleanUpWithoutDisposingWindows() {
basicRobot.cleanUpWithoutDisposingWindows()
}
override fun isReadyForInput(component: Component): Boolean = basicRobot.isReadyForInput(component)
override fun focusAndWaitForFocusGain(component: Component) {
basicRobot.focusAndWaitForFocusGain(component)
}
override fun releaseModifiers(p0: Int) {
basicRobot.releaseModifiers(p0)
}
override fun findActivePopupMenu(): JPopupMenu? {
return basicRobot.findActivePopupMenu()
}
override fun rotateMouseWheel(component: Component, p1: Int) {
basicRobot.rotateMouseWheel(component, p1)
}
override fun rotateMouseWheel(p0: Int) {
basicRobot.rotateMouseWheel(p0)
}
override fun pressAndReleaseKeys(vararg p0: Int) {
basicRobot.pressAndReleaseKeys(*p0)
}
override fun finder(): ComponentFinder = basicRobot.finder()
private val basicRobot: BasicRobot = BasicRobot.robotWithCurrentAwtHierarchyWithoutScreenLock() as BasicRobot
init {
settings().delayBetweenEvents(10)
}
private val waitConst = 30L
private var myAwareClick: Boolean = false
override fun waitForIdle() {
if (myAwareClick) {
Thread.sleep(50)
}
else {
Pause.pause(waitConst)
if (!SwingUtilities.isEventDispatchThread()) EdtInvocationManager.getInstance().invokeAndWait({ })
}
}
override fun close(w: Window) {
basicRobot.close(w)
basicRobot.waitForIdle()
}
//smooth mouse move
override fun moveMouse(x: Int, y: Int) {
val pauseConstMs = settings().delayBetweenEvents().toLong()
val n = 20
val start = MouseInfo.getPointerInfo().location
val dx = (x - start.x) / n.toDouble()
val dy = (y - start.y) / n.toDouble()
for (step in 1..n) {
try {
Pause.pause(pauseConstMs)
}
catch (e: InterruptedException) {
e.printStackTrace()
}
basicRobot.moveMouse(
(start.x + dx * ((Math.log(1.0 * step / n) - Math.log(1.0 / n)) * n / (0 - Math.log(1.0 / n)))).toInt(),
(start.y + dy * ((Math.log(1.0 * step / n) - Math.log(1.0 / n)) * n / (0 - Math.log(1.0 / n)))).toInt())
}
basicRobot.moveMouse(x, y)
}
//smooth mouse move to component
override fun moveMouse(c: Component, x: Int, y: Int) {
moveMouseWithAttempts(c, x, y)
}
//smooth mouse move for find and click actions
override fun click(c: Component, where: Point, button: MouseButton, times: Int) {
moveMouseAndClick(c, where, button, times)
}
//we are replacing BasicRobot click with our click because the original one cannot handle double click rightly (BasicRobot creates unnecessary move event between click event which breaks clickCount from 2 to 1)
override fun click(where: Point, button: MouseButton, times: Int) {
moveMouseAndClick(null, where, button, times)
}
private fun moveMouseAndClick(c: Component? = null, where: Point, button: MouseButton, times: Int) {
if (c != null) moveMouse(c, where.x, where.y) else moveMouse(where.x, where.y)
//pause between moving cursor and performing a click.
Pause.pause(waitConst)
myEdtAwareClick(button, times, where, c)
}
private fun moveMouseWithAttempts(c: Component, x: Int, y: Int, attempts: Int = 3) {
if (attempts == 0) return
waitFor { c.isShowing }
val componentLocation: Point = requireNotNull(performOnEdt { AWT.translate(c, x, y) })
moveMouse(componentLocation.x, componentLocation.y)
val componentLocationAfterMove: Point = requireNotNull(performOnEdt { AWT.translate(c, x, y) })
val mouseLocation = MouseInfo.getPointerInfo().location
if (mouseLocation.x != componentLocationAfterMove.x || mouseLocation.y != componentLocationAfterMove.y)
moveMouseWithAttempts(c, x, y, attempts - 1)
}
private fun myInnerClick(button: MouseButton, times: Int, point: Point, component: Component?) {
if (component == null)
basicRobot.click(point, button, times)
else
basicRobot.click(component, point, button, times)
}
private fun waitFor(condition: () -> Boolean) {
val timeout = 5000 //5 sec
val cdl = CountDownLatch(1)
val executor = ConcurrencyUtil.newSingleScheduledThreadExecutor("SmartWaitRobot").scheduleWithFixedDelay(Runnable {
if (condition()) {
cdl.countDown()
}
}, 0, 100, TimeUnit.MILLISECONDS)
cdl.await(timeout.toLong(), TimeUnit.MILLISECONDS)
executor.cancel(true)
}
private fun myEdtAwareClick(button: MouseButton, times: Int, point: Point, component: Component?) {
awareClick {
myInnerClick(button, times, point, component)
}
waitForIdle()
}
private fun <T> performOnEdt(body: () -> T): T? =
GuiActionRunner.execute(object : GuiQuery<T>() {
override fun executeInEDT() = body.invoke()
})
private fun awareClick(body: () -> Unit) {
myAwareClick = true
body.invoke()
myAwareClick = false
}
}
@@ -1,11 +1,11 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.ui
import org.fest.swing.core.BasicRobot
import org.fest.swing.core.GenericTypeMatcher
import org.fest.swing.core.Robot
import org.fest.swing.exception.ComponentLookupException
import org.fest.swing.exception.WaitTimedOutError
import org.fest.swing.fixture.ContainerFixture
import org.fest.swing.timing.Condition
import org.fest.swing.timing.Pause
import org.fest.swing.timing.Timeout
@@ -18,7 +18,8 @@ import java.util.concurrent.atomic.AtomicReference
object LearningUiUtil {
@Volatile
private var myRobot: Robot? = null
private val robot: Robot
val robot: Robot
get() {
if(myRobot == null)
synchronized(this) {
@@ -29,7 +30,7 @@ object LearningUiUtil {
private fun initializeRobot() {
if (myRobot != null) releaseRobot()
myRobot = BasicRobot.robotWithCurrentAwtHierarchyWithoutScreenLock() // acquires ScreenLock
myRobot = IftSmartWaitRobot()
}
private fun releaseRobot() {
@@ -119,4 +120,23 @@ object LearningUiUtil {
"Unable to find ${componentClass.simpleName} ${if (container != null) "in container $container" else ""} in ${timeout.duration()}(ms)")
}
}
/**
* function to find component of returning type inside a container (gets from receiver).
*
* @throws ComponentLookupException if desired component haven't been found under the container (gets from receiver) in specified timeout
*/
inline fun <reified ComponentType : Component, ContainerComponentType : Container> ContainerFixture<ContainerComponentType>?.findComponentWithTimeout(
timeout: Timeout = Timeout.timeout(10, TimeUnit.SECONDS),
crossinline finderFunction: (ComponentType) -> Boolean = { true }): ComponentType {
try {
return waitUntilFound(robot, this?.target() as Container?,
typeMatcher(ComponentType::class.java) { finderFunction(it) },
timeout)
}
catch (e: WaitTimedOutError) {
throw ComponentLookupException(
"Unable to find ${ComponentType::class.java.name} ${if (this?.target() != null) "in container ${this.target()}" else ""} in ${timeout.duration()}")
}
}
}
@@ -39,8 +39,7 @@ class LearningLessonsAutoExecutor(val project: Project, private val progress: Pr
val lessons = CourseManager.instance.lessonsForModules
for (lesson in lessons) {
if (lesson !is KLesson) continue
if (lesson.testScriptProperties.skipTesting) continue
if (lesson !is KLesson || lesson.testScriptProperties.skipTesting) continue
progress.checkCanceled()
runSingleLesson(lesson)
}
@@ -14,7 +14,7 @@
<orderEntry type="module" module-name="intellij.featuresTrainer" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.testGuiFramework" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
</component>
</module>
@@ -1,8 +1,6 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.ift.lesson.completion
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.LessonContext
import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -74,7 +72,7 @@ class FStringCompletionLesson
stateCheck {
editor.document.text == result
}
test { GuiTestUtil.shortcut(Key.ENTER) }
test(waitEditorToBeReady = false) { invokeActionViaShortcut("ENTER") }
}
text(PythonLessonsBundle.message("python.f.string.completion.result.message"))
}
@@ -1,8 +1,6 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.ift.lesson.completion
import com.intellij.testGuiFramework.framework.GuiTestUtil.typeText
import com.intellij.testGuiFramework.impl.jList
import com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.LessonContext
import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -48,16 +46,14 @@ class PythonPostfixCompletionLesson
checkExpectedStateOfEditor(sample) { completionSuffix.startsWith(it) }
}
test {
ideFrame {
typeText(completionSuffix)
}
type(completionSuffix)
}
}
task {
text(PythonLessonsBundle.message("python.postfix.completion.select.item", code(completionSuffix)))
stateCheck { editor.document.text == result }
restoreByUi()
test {
test(waitEditorToBeReady = false) {
ideFrame {
jList("ifnn").item(0).doubleClick()
}
@@ -1,8 +1,6 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.ift.lesson.completion
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -67,7 +65,7 @@ class PythonTabCompletionLesson
restoreAfterStateBecomeFalse {
selectNeededItem()?.not() ?: true
}
test { GuiTestUtil.shortcut(Key.TAB) }
test { invokeActionViaShortcut("TAB") }
}
}
}
@@ -2,8 +2,6 @@
package com.jetbrains.python.ift.lesson.refactorings
import com.intellij.icons.AllIcons
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -70,7 +68,7 @@ class PythonInPlaceRefactoringLesson
val expected = template.replace("<caret>", "").replace("<name>", newName)
newName != variableName && editor.document.text == expected
}
test { GuiTestUtil.shortcut(Key.ENTER) }
test { invokeActionViaShortcut("ENTER") }
}
waitBeforeContinue(500)
@@ -127,7 +125,7 @@ class PythonInPlaceRefactoringLesson
ui.javaClass.name.contains("ChangeSignaturePopup")
}
restoreByUi(delayMillis = defaultRestoreDelay)
test { GuiTestUtil.shortcut(Key.ENTER) }
test { invokeActionViaShortcut("ENTER") }
}
task {
@@ -138,7 +136,7 @@ class PythonInPlaceRefactoringLesson
restoreAfterStateBecomeFalse(restoreId = showIntentionsTaskId) {
previous.ui?.isShowing != true
}
test { GuiTestUtil.shortcut(Key.ENTER) }
test(waitEditorToBeReady = false) { invokeActionViaShortcut("ENTER") }
}
task {
lateinit var beforeSecondRefactoring: String
@@ -152,9 +150,9 @@ class PythonInPlaceRefactoringLesson
it.className.contains("PySuggestedRefactoringExecution") && it.methodName == "performChangeSignature"
}
}
test {
test(waitEditorToBeReady = false) {
type("0")
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
text(PythonLessonsBundle.message("python.in.place.refactoring.remark.about.application.scope"))
@@ -6,9 +6,6 @@ import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.util.Key
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.table.JBTableRow
import com.jetbrains.python.PyBundle
@@ -51,7 +48,7 @@ class PythonQuickFixesRefactoringLesson
restoreState {
!editor.document.text.contains(it)
}
test { GuiTestUtil.shortcut(Key.ESCAPE) }
test(waitEditorToBeReady = false) { invokeActionViaShortcut("ESCAPE") }
}
prepareRuntimeTask { // restore point
@@ -86,7 +83,7 @@ class PythonQuickFixesRefactoringLesson
else null
}
restoreByUi(delayMillis = defaultRestoreDelay)
test {
test(waitEditorToBeReady = false) {
ideFrame {
jListContains(quickFixItemText).clickItem(Pattern.compile(".*$quickFixItemText.*"))
}
@@ -103,12 +100,12 @@ class PythonQuickFixesRefactoringLesson
UIUtil.getParentOfType(JDialog::class.java, editor) != null
}
restoreByUi()
test {
test(waitEditorToBeReady = false) {
invokeAndWaitIfNeeded(ModalityState.any()) {
val ui = previous.ui ?: return@invokeAndWaitIfNeeded
IdeFocusManager.getInstance(project).requestFocus(ui, true)
}
GuiTestUtil.shortcut(Key.ENTER)
invokeActionViaShortcut("ENTER")
}
}
task {
@@ -118,12 +115,12 @@ class PythonQuickFixesRefactoringLesson
stateCheck {
(previous.ui as? EditorComponentImpl)?.text == "0"
}
test {
test(waitEditorToBeReady = false) {
invokeAndWaitIfNeeded(ModalityState.any()) {
val ui = previous.ui ?: return@invokeAndWaitIfNeeded
IdeFocusManager.getInstance(project).requestFocus(ui, true)
}
GuiTestUtil.shortcut(Key.BACK_SPACE)
invokeActionViaShortcut("BACK_SPACE")
type("0")
}
}
@@ -144,11 +141,9 @@ class PythonQuickFixesRefactoringLesson
!stackInsideDialogRefactoring()
}
test {
with(TaskTestContext.guiTestCase) {
dialog("Change Signature") {
button("Refactor").click()
}
test(waitEditorToBeReady = false) {
dialog("Change Signature") {
button("Refactor").click()
}
}
}
@@ -3,17 +3,12 @@ package com.jetbrains.python.ift.lesson.refactorings
import com.intellij.ide.DataManager
import com.intellij.ide.actions.exclusion.ExclusionHandler
import com.intellij.openapi.application.runReadAction
import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.framework.Timeouts
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.impl.jTree
import com.intellij.testGuiFramework.util.Key
import com.intellij.ui.tree.TreeVisitor
import com.intellij.usageView.UsageViewBundle
import com.intellij.util.ui.tree.TreeUtil
import com.jetbrains.python.ift.PythonLessonsBundle
import org.fest.swing.fixture.JTreeFixture
import org.jetbrains.annotations.Nullable
import training.dsl.*
import training.learn.LessonsBundle
@@ -24,6 +19,7 @@ import javax.swing.JTree
import javax.swing.tree.TreePath
class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesson.name")) {
override val testScriptProperties = TaskTestContext.TestScriptProperties(10)
private val template = """
class Championship:
def __init__(self):
@@ -55,9 +51,6 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
print(c.<name>)
""".trimIndent() + '\n'
/** For test only */
private val substringPredicate: (String, String) -> Boolean = { found: String, wanted: String -> found.contains(wanted) }
private val sample = parseLessonSample(template.replace("<name>", "teams"))
private val replacePreviewPattern = Pattern.compile(".*Variable to be renamed to (\\w+).*")
@@ -66,10 +59,13 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
prepareSample(sample)
val dynamicWord = UsageViewBundle.message("usage.view.results.node.dynamic")
var replace: String? = null
var dynamicItem: String? = null
task("RenameElement") {
text(PythonLessonsBundle.message("python.rename.press.rename", action(it), code("teams"), code("teams_number")))
triggerByFoundPathAndHighlight { tree: JTree, path: TreePath ->
if (path.pathCount == 2 && path.getPathComponent(1).toString().contains(dynamicWord)) {
val pathStr = path.getPathComponent(1).toString()
if (path.pathCount == 2 && pathStr.contains(dynamicWord)) {
dynamicItem = pathStr
replace = replacePreviewPattern.matcher(tree.model.root.toString()).takeIf { m -> m.find() }?.group(1)
true
}
@@ -77,11 +73,9 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
}
test {
actions(it)
with(TaskTestContext.guiTestCase) {
dialog {
typeText("teams_number")
button("Refactor").click()
}
dialog {
type("teams_number")
button("Refactor").click()
}
}
}
@@ -102,12 +96,14 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
}
showWarningIfFindToolbarClosed()
test {
//TODO: Fix tree access
val jTree = previous.ui as? JTree ?: return@test
val di = dynamicItem ?: return@test
ideFrame {
val jTree = runReadAction {
jTree(dynamicReferencesString, timeout = Timeouts.seconds03, predicate = substringPredicate)
}
val jTreeFixture = JTreeFixture(robot, jTree)
jTreeFixture.replaceSeparator("@@@")
jTreeFixture.expandPath(di)
// WARNING: several exception will be here because of UsageNode#toString inside info output during this operation
jTree.doubleClickPath()
}
}
}
@@ -131,8 +127,8 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
showWarningIfFindToolbarClosed()
test {
ideFrame {
type("co_me")
GuiTestUtil.shortcut(Key.DELETE)
type("come")
invokeActionViaShortcut("DELETE")
}
}
}
@@ -149,7 +145,7 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
text(PythonLessonsBundle.message("python.rename.finish.refactoring", strong(confirmRefactoringButton)))
stateCheck { editor.document.text == result }
showWarningIfFindToolbarClosed()
test {
test(waitEditorToBeReady = false) {
ideFrame {
button(confirmRefactoringButton).click()
}