[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-17 15:05:42 +03:00
committed by intellij-monorepo-bot
parent a088f477b2
commit 4cbe1d21b1
38 changed files with 771 additions and 242 deletions

View File

@@ -16,6 +16,6 @@
<orderEntry type="module" module-name="intellij.featuresTrainer" /> <orderEntry type="module" module-name="intellij.featuresTrainer" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" /> <orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" /> <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> </component>
</module> </module>

View File

@@ -10,8 +10,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.wm.ToolWindowId import com.intellij.openapi.wm.ToolWindowId
import com.intellij.openapi.wm.ToolWindowManager import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.impl.content.BaseLabel 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.InplaceButton
import com.intellij.ui.UIBundle import com.intellij.ui.UIBundle
import training.dsl.* import training.dsl.*
@@ -43,7 +41,7 @@ class JavaInheritanceHierarchyLesson
test { test {
Thread.sleep(1000) Thread.sleep(1000)
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }

View File

@@ -3,9 +3,6 @@ package com.intellij.java.ift.lesson.navigation
import com.intellij.find.SearchTextArea import com.intellij.find.SearchTextArea
import com.intellij.java.ift.JavaLessonsBundle 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 com.intellij.usageView.UsageViewBundle
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil import training.dsl.LessonUtil
@@ -95,7 +92,7 @@ class JavaOccurrencesLesson
stateCheck { stateCheck {
editor.headerComponent == null editor.headerComponent == null
} }
test { GuiTestUtil.shortcut(Key.ESCAPE) } test { invokeActionViaShortcut("ESCAPE") }
} }
actionTask("FindNext") { actionTask("FindNext") {
JavaLessonsBundle.message("java.find.occurrences.find.next.in.editor", action(it)) JavaLessonsBundle.message("java.find.occurrences.find.next.in.editor", action(it))

View File

@@ -4,11 +4,9 @@ package com.intellij.java.ift.lesson.refactorings
import com.intellij.java.refactoring.JavaRefactoringBundle import com.intellij.java.refactoring.JavaRefactoringBundle
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.extractMethod.ExtractMethodHandler import com.intellij.refactoring.extractMethod.ExtractMethodHandler
import com.intellij.testGuiFramework.impl.button
import com.intellij.ui.UIBundle import com.intellij.ui.UIBundle
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.dsl.TaskTestContext
import training.dsl.dropMnemonic import training.dsl.dropMnemonic
import training.dsl.parseLessonSample import training.dsl.parseLessonSample
import training.learn.LessonsBundle import training.learn.LessonsBundle
@@ -46,10 +44,8 @@ class JavaExtractMethodCocktailSortLesson
} }
test { test {
with(TaskTestContext.guiTestCase) { dialog(RefactoringBundle.message("extract.method.title")) {
dialog(RefactoringBundle.message("extract.method.title"), needToKeepDialog=true) { button(refactorButtonText).click()
button(refactorButtonText).click()
}
} }
} }
} }
@@ -63,10 +59,8 @@ class JavaExtractMethodCocktailSortLesson
} }
test { test {
with(TaskTestContext.guiTestCase) { dialog(processDuplicatesTitle) {
dialog(processDuplicatesTitle) { button(replaceButtonText).click()
button(replaceButtonText).click()
}
} }
} }
} }

View File

@@ -4,8 +4,6 @@ package com.intellij.java.ift.lesson.refactorings
import com.intellij.CommonBundle import com.intellij.CommonBundle
import com.intellij.java.ift.JavaLessonsBundle import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil import training.dsl.LessonUtil
@@ -88,8 +86,8 @@ class JavaRefactoringMenuLesson : RefactoringMenuLessonBase("java.refactoring.me
stateCheck { stateCheck {
!extractConstantDialogShowing() !extractConstantDialogShowing()
} }
test { test(waitEditorToBeReady = false) {
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
} }

View File

@@ -6,7 +6,6 @@ import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.java.ift.JavaLessonsBundle import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.java.refactoring.JavaRefactoringBundle import com.intellij.java.refactoring.JavaRefactoringBundle
import com.intellij.refactoring.rename.RenameProcessor import com.intellij.refactoring.rename.RenameProcessor
import com.intellij.testGuiFramework.impl.button
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import training.dsl.* import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor import training.dsl.LessonUtil.checkExpectedStateOfEditor

View File

@@ -4,13 +4,15 @@ package com.intellij.java.ift.lesson.run
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.java.ift.JavaLessonsBundle import com.intellij.java.ift.JavaLessonsBundle
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.testGuiFramework.impl.button
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.TaskTestContext import training.dsl.TaskTestContext
import training.dsl.highlightButtonById import training.dsl.highlightButtonById
import training.learn.lesson.general.run.CommonDebugLesson import training.learn.lesson.general.run.CommonDebugLesson
class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") { class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") {
override val testScriptProperties = TaskTestContext.TestScriptProperties(duration = 30)
private val demoClassName = JavaRunLessonsUtils.demoClassName private val demoClassName = JavaRunLessonsUtils.demoClassName
override val configurationName: String = demoClassName override val configurationName: String = demoClassName
override val sample = JavaRunLessonsUtils.demoSample override val sample = JavaRunLessonsUtils.demoSample
@@ -41,11 +43,9 @@ class JavaDebugLesson : CommonDebugLesson("java.debug.workflow") {
!inHotSwapDialog() !inHotSwapDialog()
} }
proposeModificationRestore(afterFixText) proposeModificationRestore(afterFixText)
test { test(waitEditorToBeReady = false) {
with(TaskTestContext.guiTestCase) { dialog(null) {
dialog(null, needToKeepDialog = true) { button("Reload").click()
button("Yes").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" override val fileName: String = "$demoClassName.java"
} }

View File

@@ -12,15 +12,14 @@
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" /> <orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
<orderEntry type="library" scope="TEST" name="kotlin-test" level="project" /> <orderEntry type="library" scope="TEST" name="kotlin-test" level="project" />
<orderEntry type="library" name="Guava" level="project" /> <orderEntry type="library" name="Guava" level="project" />
<orderEntry type="library" name="fest" level="project" /> <orderEntry type="library" exported="" name="fest" level="project" />
<orderEntry type="library" name="fest-swing" 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.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.execution.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.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.diff.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" />
<orderEntry type="module" module-name="intellij.platform.debugger.impl" /> <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.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.statistics" /> <orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.tasks.impl" /> <orderEntry type="module" module-name="intellij.platform.tasks.impl" />

View File

@@ -102,7 +102,7 @@ abstract class TaskContext : LearningDslBase {
open fun addStep(step: CompletableFuture<Boolean>) = Unit open fun addStep(step: CompletableFuture<Boolean>) = Unit
/** [action] What should be done to pass the current task */ /** [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, fun triggerByFoundPathAndHighlight(highlightBorder: Boolean = true, highlightInside: Boolean = false, usePulsation: Boolean = false,
checkPath: TaskRuntimeContext.(tree: JTree, path: TreePath) -> Boolean) { checkPath: TaskRuntimeContext.(tree: JTree, path: TreePath) -> Boolean) {

View File

@@ -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. // 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 package training.dsl
import com.intellij.ide.util.treeView.NodeRenderer
import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.testGuiFramework.cellReader.ExtendedJListCellReader import com.intellij.openapi.actionSystem.impl.ActionButton
import com.intellij.testGuiFramework.fixtures.ComponentFixture import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.testGuiFramework.fixtures.IdeFrameFixture import com.intellij.openapi.wm.impl.IdeFrameImpl
import com.intellij.testGuiFramework.framework.GuiTestUtil import com.intellij.ui.KeyStrokeAdapter
import com.intellij.testGuiFramework.framework.Timeouts import com.intellij.ui.MultilineTreeCellRenderer
import com.intellij.testGuiFramework.impl.GuiTestCase import com.intellij.ui.SimpleColoredComponent
import com.intellij.testGuiFramework.impl.findComponentWithTimeout import org.fest.swing.core.GenericTypeMatcher
import com.intellij.testGuiFramework.impl.waitUntilFound
import com.intellij.testGuiFramework.util.step
import org.fest.swing.core.Robot 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.ContainerFixture
import org.fest.swing.fixture.JButtonFixture
import org.fest.swing.fixture.JListFixture 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 org.fest.swing.timing.Timeout
import training.ui.LearningUiUtil
import training.ui.LearningUiUtil.findComponentWithTimeout
import training.util.invokeActionForFocusContext import training.util.invokeActionForFocusContext
import java.awt.Component import java.awt.Component
import java.awt.Container 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 javax.swing.JList
import kotlin.collections.ArrayList
@LearningDsl @LearningDsl
class TaskTestContext(rt: TaskRuntimeContext): TaskRuntimeContext(rt) { class TaskTestContext(rt: TaskRuntimeContext): TaskRuntimeContext(rt) {
private val defaultTimeout = Timeout.timeout(3, TimeUnit.SECONDS)
val robot: Robot get() = LearningUiUtil.robot
data class TestScriptProperties ( data class TestScriptProperties (
val duration: Int = 6, //seconds val duration: Int = 15, //seconds
val skipTesting: Boolean = false val skipTesting: Boolean = false
) )
fun type(text: String) { 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) { fun actions(vararg actionIds: String) {
@@ -39,50 +62,274 @@ class TaskTestContext(rt: TaskRuntimeContext): TaskRuntimeContext(rt) {
} }
} }
fun ideFrame(action: IdeFrameFixture.() -> Unit) { fun ideFrame(action: ContainerFixture<IdeFrameImpl>.() -> Unit) {
with(guiTestCase) { with(findIdeFrame(robot, defaultTimeout)) {
ideFrame { action()
// Note: It is not recursive call here. It is GuiTestCase#ideFrame
action()
}
} }
} }
fun <ComponentType : Component> waitComponent(componentClass: Class<ComponentType>, partOfName: String? = null) { fun <ComponentType : Component> waitComponent(componentClass: Class<ComponentType>, partOfName: String? = null) {
waitUntilFound(null, componentClass, Timeouts.seconds02) { LearningUiUtil.waitUntilFound(robot, null, typeMatcher(componentClass) {
(if (partOfName != null) it.javaClass.name.contains(partOfName) else true) && it.isShowing (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 // Modified copy-paste
fun <C : Container> ContainerFixture<C>.jListContains(partOfItem: String? = null, timeout: Timeout = Timeouts.seconds02): JListFixture { fun <C : Container> ContainerFixture<C>.jListContains(partOfItem: String? = null, timeout: Timeout = defaultTimeout): JListFixture {
return step("search '$partOfItem' in list") { return generalListFinder(timeout, partOfItem) { element, p -> element.contains(p) }
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
}
} }
/**
* 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>( fun ContainerFixture<*>.jComponent(target: Component): AbstractComponentFixture<*, Component, ComponentDriver<*>> {
SimpleComponentFixture::class.java, robot, target)
fun IdeFrameFixture.jComponent(target: Component): SimpleComponentFixture {
return SimpleComponentFixture(robot(), target) return SimpleComponentFixture(robot(), target)
} }
fun invokeActionViaShortcut(shortcut: String) {
val keyStroke = KeyStrokeAdapter.getKeyStroke(shortcut)
robot.pressAndReleaseKey(keyStroke.keyCode, keyStroke.modifiers)
}
companion object { companion object {
@Volatile @Volatile
var inTestMode: Boolean = false 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())
} }
} }

View File

@@ -309,7 +309,7 @@ class LessonExecutor(val lesson: KLesson, val project: Project, initialEditor: E
} }
private fun processTestActions(taskContext: TaskContextImpl) { private fun processTestActions(taskContext: TaskContextImpl) {
if (TaskTestContext.inTestMode) { if (TaskTestContext.inTestMode && taskContext.testActions.isNotEmpty()) {
LessonManager.instance.testActionsExecutor.execute { LessonManager.instance.testActionsExecutor.execute {
taskContext.testActions.forEach { it.run() } taskContext.testActions.forEach { it.run() }
} }

View File

@@ -14,7 +14,6 @@ import com.intellij.util.Alarm
import training.dsl.LearningBalloonConfig import training.dsl.LearningBalloonConfig
import training.dsl.TaskContext import training.dsl.TaskContext
import training.dsl.TaskRuntimeContext import training.dsl.TaskRuntimeContext
import training.dsl.TaskTestContext
import training.learn.ActionsRecorder import training.learn.ActionsRecorder
import training.learn.LearnBundle import training.learn.LearnBundle
import training.learn.lesson.LessonManager import training.learn.lesson.LessonManager
@@ -189,8 +188,6 @@ private class ExtractTaskPropertiesContext(override val project: Project) : Task
hasDetection = true 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 action(actionId: String): String = "" //Doesn't matter what to return
override fun code(sourceSample: String): String = "" //Doesn't matter what to return override fun code(sourceSample: String): String = "" //Doesn't matter what to return

View File

@@ -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. // 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 package training.dsl.impl
import com.intellij.openapi.application.ApplicationManager import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.*
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
@@ -222,9 +220,21 @@ internal class TaskContextImpl(private val lessonExecutor: LessonExecutor,
steps.add(step) steps.add(step)
} }
override fun test(action: TaskTestContext.() -> Unit) { override fun test(waitEditorToBeReady: Boolean, action: TaskTestContext.() -> Unit) {
testActions.add(Runnable { testActions.add(Runnable {
DumbService.getInstance(runtimeContext.project).waitForSmartMode() 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() TaskTestContext(runtimeContext).action()
}) })
} }

View File

@@ -9,14 +9,13 @@ import com.intellij.openapi.editor.actions.ToggleShowLineNumbersGloballyAction
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.editor.impl.EditorComponentImpl import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.util.SystemInfo 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 com.intellij.util.ui.UIUtil
import org.fest.swing.driver.ComponentDriver
import training.dsl.* import training.dsl.*
import training.learn.LearnBundle import training.learn.LearnBundle
import training.learn.LessonsBundle import training.learn.LessonsBundle
import training.learn.course.KLesson import training.learn.course.KLesson
import java.awt.Component
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.JPanel 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 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 // Note 2: In order to pass this task without interference with later task I need to firstly focus lesson
// and only then press Escape // and only then press Escape
editor.requestFocus() ComponentDriver<Component>(robot).focusAndWaitForFocusGain(editor.contentComponent)
GuiTestUtil.shortcut(Key.ESCAPE) invokeActionViaShortcut("ESCAPE")
} }
} }
} }

View File

@@ -4,7 +4,6 @@ package training.learn.lesson.general.assistance
import com.intellij.codeInsight.CodeInsightBundle import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.ide.util.PropertiesComponent import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.editor.impl.EditorComponentImpl import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.testGuiFramework.impl.button
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonSample import training.dsl.LessonSample
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -59,7 +58,7 @@ class CodeFormatLesson(private val sample: LessonSample, private val optimizeImp
restoreAfterStateBecomeFalse { restoreAfterStateBecomeFalse {
focusOwner is EditorComponentImpl focusOwner is EditorComponentImpl
} }
test { test(waitEditorToBeReady = false) {
properties.setValue("LayoutCode.optimizeImports", true) properties.setValue("LayoutCode.optimizeImports", true)
ideFrame { ideFrame {
button("Run").click() button("Run").click()

View File

@@ -2,11 +2,6 @@
package training.learn.lesson.general.assistance package training.learn.lesson.general.assistance
import com.intellij.ide.IdeBundle 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.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
import training.learn.LessonsBundle import training.learn.LessonsBundle
@@ -59,9 +54,8 @@ abstract class EditorCodingAssistanceLesson(private val sample: LessonSample) :
restoreIfModifiedOrMoved() restoreIfModifiedOrMoved()
test { test {
Thread.sleep(500) Thread.sleep(500)
val errorDescriptionShortcut = Shortcut(hashSetOf(Modifier.CONTROL), Key.F1) invokeActionViaShortcut("CONTROL F1")
GuiTestUtil.shortcut(errorDescriptionShortcut) invokeActionViaShortcut("CONTROL F1")
GuiTestUtil.shortcut(errorDescriptionShortcut)
} }
} }

View File

@@ -4,8 +4,6 @@ package training.learn.lesson.general.assistance
import com.intellij.codeInsight.documentation.DocumentationComponent import com.intellij.codeInsight.documentation.DocumentationComponent
import com.intellij.codeInsight.documentation.QuickDocUtil import com.intellij.codeInsight.documentation.QuickDocUtil
import com.intellij.codeInsight.hint.ImplementationViewComponent 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.LessonContext
import training.dsl.LessonSample import training.dsl.LessonSample
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -31,7 +29,7 @@ class QuickPopupsLesson(private val sample: LessonSample) :
stateCheck { checkDocComponentClosed() } stateCheck { checkDocComponentClosed() }
restoreIfModifiedOrMoved() restoreIfModifiedOrMoved()
test { test {
GuiTestUtil.shortcut(Key.ESCAPE) invokeActionViaShortcut("ESCAPE")
} }
} }

View File

@@ -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. // 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 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.LessonContext
import training.dsl.LessonSample import training.dsl.LessonSample
import training.dsl.LessonUtil import training.dsl.LessonUtil
@@ -46,7 +44,7 @@ abstract class BasicCompletionLessonBase : KLesson("Basic completion", LessonsBu
} }
} }
test { test {
GuiTestUtil.typeText(item1StartToType) type(item1StartToType)
} }
} }
task("EditorChooseLookupItem") { task("EditorChooseLookupItem") {
@@ -59,8 +57,8 @@ abstract class BasicCompletionLessonBase : KLesson("Basic completion", LessonsBu
!isTheFirstVariant(ui) !isTheFirstVariant(ui)
} ?: true } ?: true
} }
test { test(waitEditorToBeReady = false) {
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
waitBeforeContinue(500) waitBeforeContinue(500)

View File

@@ -8,8 +8,6 @@ import com.intellij.openapi.wm.impl.content.BaseLabel
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile 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.UIBundle
import com.intellij.ui.table.JBTable import com.intellij.ui.table.JBTable
import training.dsl.LessonContext import training.dsl.LessonContext
@@ -56,7 +54,7 @@ abstract class DeclarationAndUsagesLesson
actions(it) actions(it)
ideFrame { ideFrame {
waitComponent(JBTable::class.java, "ShowUsagesTable") waitComponent(JBTable::class.java, "ShowUsagesTable")
shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
} }

View File

@@ -4,8 +4,6 @@ package training.learn.lesson.general.navigation
import com.intellij.ide.dnd.aware.DnDAwareTree import com.intellij.ide.dnd.aware.DnDAwareTree
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.impl.EditorComponentImpl 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 com.intellij.ui.speedSearch.SpeedSearchSupply
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil import training.dsl.LessonUtil
@@ -48,7 +46,7 @@ abstract class FileStructureLesson
text(LessonsBundle.message("file.structure.navigate", LessonUtil.rawEnter())) text(LessonsBundle.message("file.structure.navigate", LessonUtil.rawEnter()))
stateCheck { editor.caretModel.logicalPosition == methodToFindPosition } stateCheck { editor.caretModel.logicalPosition == methodToFindPosition }
restoreState { !checkWordInSearch(searchSubstring) } restoreState { !checkWordInSearch(searchSubstring) }
test { GuiTestUtil.shortcut(Key.ENTER) } test { invokeActionViaShortcut("ENTER") }
} }
task("ActivateStructureToolWindow") { task("ActivateStructureToolWindow") {
text(LessonsBundle.message("file.structure.toolwindow", action(it))) text(LessonsBundle.message("file.structure.toolwindow", action(it)))

View File

@@ -9,20 +9,16 @@ import com.intellij.find.impl.FindInProjectSettingsBase
import com.intellij.find.impl.FindPopupPanel import com.intellij.find.impl.FindPopupPanel
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.impl.ActionButton 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.usages.UsagePresentation
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import org.fest.swing.core.MouseClickInfo import org.fest.swing.core.MouseClickInfo
import org.fest.swing.data.TableCell import org.fest.swing.data.TableCell
import org.fest.swing.fixture.JTableFixture
import org.fest.swing.fixture.JTextComponentFixture import org.fest.swing.fixture.JTextComponentFixture
import training.dsl.* import training.dsl.*
import training.learn.LessonsBundle import training.learn.LessonsBundle
import training.learn.course.KLesson import training.learn.course.KLesson
import training.ui.LearningUiUtil.findComponentWithTimeout
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.* import javax.swing.*
@@ -42,7 +38,6 @@ class FindInFilesLesson(override val existedFile: String)
!popup.helper.isReplaceState !popup.helper.isReplaceState
} }
test { test {
Thread.sleep(300)
actions(it) actions(it)
} }
} }
@@ -61,7 +56,7 @@ class FindInFilesLesson(override val existedFile: String)
LessonUtil.rawKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.ALT_DOWN_MASK)))) LessonUtil.rawKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.ALT_DOWN_MASK))))
highlightAndTriggerWhenButtonSelected(wholeWordsButtonText) highlightAndTriggerWhenButtonSelected(wholeWordsButtonText)
showWarningIfPopupClosed(false) showWarningIfPopupClosed(false)
test { test(waitEditorToBeReady = false) {
ideFrame { ideFrame {
actionButton(wholeWordsButtonText).click() actionButton(wholeWordsButtonText).click()
} }
@@ -84,9 +79,8 @@ class FindInFilesLesson(override val existedFile: String)
restoreByUi(restoreId = showPopupTaskId) restoreByUi(restoreId = showPopupTaskId)
test { test {
ideFrame { ideFrame {
Thread.sleep(300)
val table = findComponentWithTimeout { table: JTable -> table.findLastRowIndexOfItemWithText(it) != -1 } val table = findComponentWithTimeout { table: JTable -> table.findLastRowIndexOfItemWithText(it) != -1 }
val tableFixture = ExtendedTableFixture(robot(), table) val tableFixture = JTableFixture(robot(), table)
val rowIndex = table.findLastRowIndexOfItemWithText(it) val rowIndex = table.findLastRowIndexOfItemWithText(it)
tableFixture.click(TableCell.row(rowIndex).column(0), MouseClickInfo.leftButton()) 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())) text(LessonsBundle.message("find.in.files.go.to.file", LessonUtil.rawEnter()))
stateCheck { virtualFile.name != existedFile.substringAfterLast('/') } stateCheck { virtualFile.name != existedFile.substringAfterLast('/') }
restoreByUi(restoreId = showPopupTaskId) restoreByUi(restoreId = showPopupTaskId)
test { GuiTestUtil.shortcut(Key.ENTER) } test { invokeActionViaShortcut("ENTER") }
} }
task("ReplaceInPath") { task("ReplaceInPath") {
@@ -157,7 +151,6 @@ class FindInFilesLesson(override val existedFile: String)
showWarningIfPopupClosed(true) showWarningIfPopupClosed(true)
test { test {
ideFrame { ideFrame {
Thread.sleep(300)
button(replaceAllButtonText).click() button(replaceAllButtonText).click()
} }
} }
@@ -168,10 +161,9 @@ class FindInFilesLesson(override val existedFile: String)
text(LessonsBundle.message("find.in.files.confirm.replace", strong(replaceButtonText))) text(LessonsBundle.message("find.in.files.confirm.replace", strong(replaceButtonText)))
stateCheck { editor.document.charsSequence.contains("orange") } stateCheck { editor.document.charsSequence.contains("orange") }
restoreByUi(delayMillis = defaultRestoreDelay) restoreByUi(delayMillis = defaultRestoreDelay)
test { test(waitEditorToBeReady = false) {
ideFrame { dialog(title = "Replace All") {
Thread.sleep(300) button(replaceButtonText).click()
findMessageDialog(replaceAllDialogTitle).click(replaceButtonText)
} }
} }
} }

View File

@@ -7,8 +7,6 @@ import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.Messages
import com.intellij.openapi.wm.IdeFrame 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.SearchTextField
import com.intellij.ui.components.JBList import com.intellij.ui.components.JBList
import com.intellij.ui.components.fields.ExtendableTextField import com.intellij.ui.components.fields.ExtendableTextField
@@ -89,7 +87,9 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreState { restoreState {
!checkRecentFilesSearch("rfd") || previous.ui?.isShowing != true !checkRecentFilesSearch("rfd") || previous.ui?.isShowing != true
} }
test { GuiTestUtil.shortcut(Key.ENTER) } test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
}
} }
actionTask("RecentFiles") { actionTask("RecentFiles") {
@@ -112,7 +112,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreIfRecentFilesPopupClosed() restoreIfRecentFilesPopupClosed()
test { test {
repeat(countOfFilesToDelete) { repeat(countOfFilesToDelete) {
GuiTestUtil.shortcut(Key.DELETE) invokeActionViaShortcut("DELETE")
} }
} }
} }
@@ -120,7 +120,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
task { task {
text(LessonsBundle.message("recent.files.close.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE))) text(LessonsBundle.message("recent.files.close.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE)))
stateCheck { focusOwner is IdeFrame } stateCheck { focusOwner is IdeFrame }
test { GuiTestUtil.shortcut(Key.ESCAPE) } test { invokeActionViaShortcut("ESCAPE") }
} }
actionTask("RecentLocations") { actionTask("RecentLocations") {
@@ -146,7 +146,7 @@ abstract class RecentFilesLesson : KLesson("Recent Files and Locations", Lessons
restoreState { restoreState {
!checkRecentLocationsSearch(stringForRecentFilesSearch) || previous.ui?.isShowing != true !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 { private fun checkWordInSearch(expected: String, component: JComponent): Boolean {
val supply = SpeedSearchSupply.getSupply(component) val supply = SpeedSearchSupply.getSupply(component)
val enteredPrefix = supply?.enteredPrefix ?: return false val enteredPrefix = supply?.enteredPrefix ?: return false
val equals = enteredPrefix.equals(expected, ignoreCase = true) return enteredPrefix.equals(expected, ignoreCase = true)
System.err.println("expected = '$expected', enteredPrefix = '$enteredPrefix', equals = $equals")
return equals
} }
private fun TaskContext.restoreIfRecentFilesPopupClosed() { private fun TaskContext.restoreIfRecentFilesPopupClosed() {

View File

@@ -8,10 +8,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.psi.search.EverythingGlobalScope import com.intellij.psi.search.EverythingGlobalScope
import com.intellij.psi.search.ProjectScope 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.ui.components.fields.ExtendableTextField
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import training.dsl.* import training.dsl.*
@@ -52,7 +48,7 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
!checkInsideSearchEverywhere() !checkInsideSearchEverywhere()
} }
test { test {
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
@@ -79,7 +75,7 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
!checkInsideSearchEverywhere() && focusOwner !is JList<*> !checkInsideSearchEverywhere() && focusOwner !is JList<*>
} }
test { 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 { task {
text(LessonsBundle.message("search.everywhere.close.documentation.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE))) text(LessonsBundle.message("search.everywhere.close.documentation.popup", LessonUtil.rawKeyStroke(KeyEvent.VK_ESCAPE)))
stateCheck { previous.ui?.isShowing != true } stateCheck { previous.ui?.isShowing != true }
test { GuiTestUtil.shortcut(Key.ESCAPE) } test { invokeActionViaShortcut("ENTER") }
} }
task { task {
@@ -103,8 +99,8 @@ abstract class SearchEverywhereLesson : KLesson("Search everywhere", LessonsBund
if (TaskTestContext.inTestMode) task { if (TaskTestContext.inTestMode) task {
stateCheck { focusOwner is EditorComponentImpl } stateCheck { focusOwner is EditorComponentImpl }
test { test {
GuiTestUtil.shortcut(Key.ESCAPE) invokeActionViaShortcut("ESCAPE")
GuiTestUtil.shortcut(Key.ESCAPE) invokeActionViaShortcut("ESCAPE")
} }
} }

View File

@@ -3,7 +3,6 @@ package training.learn.lesson.general.refactorings
import com.intellij.CommonBundle import com.intellij.CommonBundle
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.testGuiFramework.impl.button
import com.intellij.ui.UIBundle import com.intellij.ui.UIBundle
import training.dsl.* import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -43,11 +42,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
} }
restoreByUi(delayMillis = defaultRestoreDelay) restoreByUi(delayMillis = defaultRestoreDelay)
test { test(waitEditorToBeReady = false) {
with(TaskTestContext.guiTestCase) { dialog(extractMethodDialogTitle) {
dialog(extractMethodDialogTitle, needToKeepDialog = true) { button(okButtonText).click()
button(okButtonText).click()
}
} }
} }
} }
@@ -61,11 +58,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
} }
restoreByUi(restoreId = startTaskId, delayMillis = defaultRestoreDelay) restoreByUi(restoreId = startTaskId, delayMillis = defaultRestoreDelay)
test { test(waitEditorToBeReady = false) {
with(TaskTestContext.guiTestCase) { dialog(extractMethodDialogTitle) {
dialog(extractMethodDialogTitle) { button(yesButtonText).click()
button(yesButtonText).click()
}
} }
} }
} }
@@ -76,11 +71,9 @@ class ExtractMethodCocktailSortLesson(private val sample: LessonSample)
previous.ui?.isShowing?.not() ?: true previous.ui?.isShowing?.not() ?: true
} }
test { test(waitEditorToBeReady = false) {
with(TaskTestContext.guiTestCase) { dialog(replaceFragmentDialogTitle) {
dialog(replaceFragmentDialogTitle) { button(UIBundle.message("replace.prompt.replace.button").dropMnemonic()).click()
button(UIBundle.message("replace.prompt.replace.button").dropMnemonic()).click()
}
} }
} }
} }

View File

@@ -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. // 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 package training.learn.lesson.general.refactorings
import com.intellij.testGuiFramework.impl.jList
import com.intellij.ui.components.JBList import com.intellij.ui.components.JBList
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonSample 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 //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)
}
} }
} }
} }

View File

@@ -3,8 +3,6 @@ package training.learn.lesson.general.refactorings
import com.intellij.idea.ActionsBundle import com.intellij.idea.ActionsBundle
import com.intellij.refactoring.rename.inplace.InplaceRefactoring 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 com.intellij.ui.components.JBList
import training.dsl.* import training.dsl.*
import training.dsl.LessonUtil.restoreIfModifiedOrMoved import training.dsl.LessonUtil.restoreIfModifiedOrMoved
@@ -38,7 +36,7 @@ abstract class RefactoringMenuLessonBase(lessonId: String) : KLesson(lessonId, L
stateCheck { hasInplaceRename() } stateCheck { hasInplaceRename() }
restoreState(delayMillis = defaultRestoreDelay, restoreId = showPopupTaskId) { focusOwner !is JBList<*> } restoreState(delayMillis = defaultRestoreDelay, restoreId = showPopupTaskId) { focusOwner !is JBList<*> }
test { test {
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
@@ -47,8 +45,8 @@ abstract class RefactoringMenuLessonBase(lessonId: String) : KLesson(lessonId, L
stateCheck { stateCheck {
!hasInplaceRename() !hasInplaceRename()
} }
test { test(waitEditorToBeReady = false) {
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
} }

View File

@@ -19,8 +19,6 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.tasks.TaskBundle 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.util.ui.UIUtil
import com.intellij.xdebugger.* import com.intellij.xdebugger.*
import com.intellij.xdebugger.impl.XDebugSessionImpl import com.intellij.xdebugger.impl.XDebugSessionImpl
@@ -196,7 +194,7 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
proposeModificationRestore(sample.text) proposeModificationRestore(sample.text)
test { test {
Thread.sleep(500) 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) proposeModificationRestore(sample.text)
test { test {
Thread.sleep(500) Thread.sleep(500)
GuiTestUtil.shortcut(if (stepIntoDirection == "") Key.RIGHT else Key.LEFT) invokeActionViaShortcut(if (stepIntoDirection == "") "RIGHT" else "LEFT")
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
} }
@@ -286,7 +284,7 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
else checkForBreakpoints() else checkForBreakpoints()
} }
test { test {
GuiTestUtil.shortcut(Key.ESCAPE) invokeActionViaShortcut("ESCAPE")
invokeLater { invokeLater {
WriteCommandAction.runWriteCommandAction(project) { WriteCommandAction.runWriteCommandAction(project) {
val offset = sample.text.indexOf("[0]") val offset = sample.text.indexOf("[0]")
@@ -356,6 +354,9 @@ abstract class CommonDebugLesson(id: String) : KLesson(id, LessonsBundle.message
} }
task(expressionToBeEvaluated) { task(expressionToBeEvaluated) {
before {
LearningUiHighlightingManager.clearHighlights()
}
text(LessonsBundle.message("debug.workflow.type.result", code(it), text(LessonsBundle.message("debug.workflow.type.result", code(it),
strong(XDebuggerBundle.message("xdebugger.evaluate.label.expression")))) strong(XDebuggerBundle.message("xdebugger.evaluate.label.expression"))))
stateCheck { checkWordInTextField(it) } 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 dialog?.title == XDebuggerBundle.message("xdebugger.evaluate.dialog.title") && root?.children?.size == 1
} }
proposeModificationRestore(afterFixText) proposeModificationRestore(afterFixText)
test { GuiTestUtil.shortcut(Key.ENTER) } test(waitEditorToBeReady = false) {
invokeActionViaShortcut("ENTER")
invokeActionViaShortcut("ESCAPE")
}
} }
} }

View File

@@ -5,8 +5,6 @@ import com.intellij.execution.ExecutionBundle
import com.intellij.execution.RunManager import com.intellij.execution.RunManager
import com.intellij.idea.ActionsBundle import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.impl.EditorComponentImpl 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.UIBundle
import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBCheckBox
import training.dsl.* import training.dsl.*
@@ -108,7 +106,7 @@ abstract class CommonRunConfigurationLesson(id: String) : KLesson(id, LessonsBun
stateCheck { stateCheck {
focusOwner is EditorComponentImpl focusOwner is EditorComponentImpl
} }
test { test(waitEditorToBeReady = false) {
ideFrame { ideFrame {
button("Cancel").click() button("Cancel").click()
} }

View File

@@ -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
}
}

View File

@@ -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. // 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 package training.ui
import org.fest.swing.core.BasicRobot
import org.fest.swing.core.GenericTypeMatcher import org.fest.swing.core.GenericTypeMatcher
import org.fest.swing.core.Robot import org.fest.swing.core.Robot
import org.fest.swing.exception.ComponentLookupException import org.fest.swing.exception.ComponentLookupException
import org.fest.swing.exception.WaitTimedOutError import org.fest.swing.exception.WaitTimedOutError
import org.fest.swing.fixture.ContainerFixture
import org.fest.swing.timing.Condition import org.fest.swing.timing.Condition
import org.fest.swing.timing.Pause import org.fest.swing.timing.Pause
import org.fest.swing.timing.Timeout import org.fest.swing.timing.Timeout
@@ -18,7 +18,8 @@ import java.util.concurrent.atomic.AtomicReference
object LearningUiUtil { object LearningUiUtil {
@Volatile @Volatile
private var myRobot: Robot? = null private var myRobot: Robot? = null
private val robot: Robot
val robot: Robot
get() { get() {
if(myRobot == null) if(myRobot == null)
synchronized(this) { synchronized(this) {
@@ -29,7 +30,7 @@ object LearningUiUtil {
private fun initializeRobot() { private fun initializeRobot() {
if (myRobot != null) releaseRobot() if (myRobot != null) releaseRobot()
myRobot = BasicRobot.robotWithCurrentAwtHierarchyWithoutScreenLock() // acquires ScreenLock myRobot = IftSmartWaitRobot()
} }
private fun releaseRobot() { 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)") "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()}")
}
}
} }

View File

@@ -39,8 +39,7 @@ class LearningLessonsAutoExecutor(val project: Project, private val progress: Pr
val lessons = CourseManager.instance.lessonsForModules val lessons = CourseManager.instance.lessonsForModules
for (lesson in lessons) { for (lesson in lessons) {
if (lesson !is KLesson) continue if (lesson !is KLesson || lesson.testScriptProperties.skipTesting) continue
if (lesson.testScriptProperties.skipTesting) continue
progress.checkCanceled() progress.checkCanceled()
runSingleLesson(lesson) runSingleLesson(lesson)
} }

View File

@@ -14,7 +14,7 @@
<orderEntry type="module" module-name="intellij.featuresTrainer" /> <orderEntry type="module" module-name="intellij.featuresTrainer" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" /> <orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.debugger" /> <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.lang.impl" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
</component> </component>
</module> </module>

View File

@@ -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. // 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 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 com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil.checkExpectedStateOfEditor import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -74,7 +72,7 @@ class FStringCompletionLesson
stateCheck { stateCheck {
editor.document.text == result editor.document.text == result
} }
test { GuiTestUtil.shortcut(Key.ENTER) } test(waitEditorToBeReady = false) { invokeActionViaShortcut("ENTER") }
} }
text(PythonLessonsBundle.message("python.f.string.completion.result.message")) text(PythonLessonsBundle.message("python.f.string.completion.result.message"))
} }

View File

@@ -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. // 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 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 com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.LessonContext import training.dsl.LessonContext
import training.dsl.LessonUtil.checkExpectedStateOfEditor import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -48,16 +46,14 @@ class PythonPostfixCompletionLesson
checkExpectedStateOfEditor(sample) { completionSuffix.startsWith(it) } checkExpectedStateOfEditor(sample) { completionSuffix.startsWith(it) }
} }
test { test {
ideFrame { type(completionSuffix)
typeText(completionSuffix)
}
} }
} }
task { task {
text(PythonLessonsBundle.message("python.postfix.completion.select.item", code(completionSuffix))) text(PythonLessonsBundle.message("python.postfix.completion.select.item", code(completionSuffix)))
stateCheck { editor.document.text == result } stateCheck { editor.document.text == result }
restoreByUi() restoreByUi()
test { test(waitEditorToBeReady = false) {
ideFrame { ideFrame {
jList("ifnn").item(0).doubleClick() jList("ifnn").item(0).doubleClick()
} }

View File

@@ -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. // 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 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 com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.* import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -67,7 +65,7 @@ class PythonTabCompletionLesson
restoreAfterStateBecomeFalse { restoreAfterStateBecomeFalse {
selectNeededItem()?.not() ?: true selectNeededItem()?.not() ?: true
} }
test { GuiTestUtil.shortcut(Key.TAB) } test { invokeActionViaShortcut("TAB") }
} }
} }
} }

View File

@@ -2,8 +2,6 @@
package com.jetbrains.python.ift.lesson.refactorings package com.jetbrains.python.ift.lesson.refactorings
import com.intellij.icons.AllIcons import com.intellij.icons.AllIcons
import com.intellij.testGuiFramework.framework.GuiTestUtil
import com.intellij.testGuiFramework.util.Key
import com.jetbrains.python.ift.PythonLessonsBundle import com.jetbrains.python.ift.PythonLessonsBundle
import training.dsl.* import training.dsl.*
import training.dsl.LessonUtil.checkExpectedStateOfEditor import training.dsl.LessonUtil.checkExpectedStateOfEditor
@@ -70,7 +68,7 @@ class PythonInPlaceRefactoringLesson
val expected = template.replace("<caret>", "").replace("<name>", newName) val expected = template.replace("<caret>", "").replace("<name>", newName)
newName != variableName && editor.document.text == expected newName != variableName && editor.document.text == expected
} }
test { GuiTestUtil.shortcut(Key.ENTER) } test { invokeActionViaShortcut("ENTER") }
} }
waitBeforeContinue(500) waitBeforeContinue(500)
@@ -127,7 +125,7 @@ class PythonInPlaceRefactoringLesson
ui.javaClass.name.contains("ChangeSignaturePopup") ui.javaClass.name.contains("ChangeSignaturePopup")
} }
restoreByUi(delayMillis = defaultRestoreDelay) restoreByUi(delayMillis = defaultRestoreDelay)
test { GuiTestUtil.shortcut(Key.ENTER) } test { invokeActionViaShortcut("ENTER") }
} }
task { task {
@@ -138,7 +136,7 @@ class PythonInPlaceRefactoringLesson
restoreAfterStateBecomeFalse(restoreId = showIntentionsTaskId) { restoreAfterStateBecomeFalse(restoreId = showIntentionsTaskId) {
previous.ui?.isShowing != true previous.ui?.isShowing != true
} }
test { GuiTestUtil.shortcut(Key.ENTER) } test(waitEditorToBeReady = false) { invokeActionViaShortcut("ENTER") }
} }
task { task {
lateinit var beforeSecondRefactoring: String lateinit var beforeSecondRefactoring: String
@@ -152,9 +150,9 @@ class PythonInPlaceRefactoringLesson
it.className.contains("PySuggestedRefactoringExecution") && it.methodName == "performChangeSignature" it.className.contains("PySuggestedRefactoringExecution") && it.methodName == "performChangeSignature"
} }
} }
test { test(waitEditorToBeReady = false) {
type("0") type("0")
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
text(PythonLessonsBundle.message("python.in.place.refactoring.remark.about.application.scope")) text(PythonLessonsBundle.message("python.in.place.refactoring.remark.about.application.scope"))

View File

@@ -6,9 +6,6 @@ import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.editor.impl.EditorComponentImpl import com.intellij.openapi.editor.impl.EditorComponentImpl
import com.intellij.openapi.wm.IdeFocusManager import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.refactoring.RefactoringBundle 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.UIUtil
import com.intellij.util.ui.table.JBTableRow import com.intellij.util.ui.table.JBTableRow
import com.jetbrains.python.PyBundle import com.jetbrains.python.PyBundle
@@ -51,7 +48,7 @@ class PythonQuickFixesRefactoringLesson
restoreState { restoreState {
!editor.document.text.contains(it) !editor.document.text.contains(it)
} }
test { GuiTestUtil.shortcut(Key.ESCAPE) } test(waitEditorToBeReady = false) { invokeActionViaShortcut("ESCAPE") }
} }
prepareRuntimeTask { // restore point prepareRuntimeTask { // restore point
@@ -86,7 +83,7 @@ class PythonQuickFixesRefactoringLesson
else null else null
} }
restoreByUi(delayMillis = defaultRestoreDelay) restoreByUi(delayMillis = defaultRestoreDelay)
test { test(waitEditorToBeReady = false) {
ideFrame { ideFrame {
jListContains(quickFixItemText).clickItem(Pattern.compile(".*$quickFixItemText.*")) jListContains(quickFixItemText).clickItem(Pattern.compile(".*$quickFixItemText.*"))
} }
@@ -103,12 +100,12 @@ class PythonQuickFixesRefactoringLesson
UIUtil.getParentOfType(JDialog::class.java, editor) != null UIUtil.getParentOfType(JDialog::class.java, editor) != null
} }
restoreByUi() restoreByUi()
test { test(waitEditorToBeReady = false) {
invokeAndWaitIfNeeded(ModalityState.any()) { invokeAndWaitIfNeeded(ModalityState.any()) {
val ui = previous.ui ?: return@invokeAndWaitIfNeeded val ui = previous.ui ?: return@invokeAndWaitIfNeeded
IdeFocusManager.getInstance(project).requestFocus(ui, true) IdeFocusManager.getInstance(project).requestFocus(ui, true)
} }
GuiTestUtil.shortcut(Key.ENTER) invokeActionViaShortcut("ENTER")
} }
} }
task { task {
@@ -118,12 +115,12 @@ class PythonQuickFixesRefactoringLesson
stateCheck { stateCheck {
(previous.ui as? EditorComponentImpl)?.text == "0" (previous.ui as? EditorComponentImpl)?.text == "0"
} }
test { test(waitEditorToBeReady = false) {
invokeAndWaitIfNeeded(ModalityState.any()) { invokeAndWaitIfNeeded(ModalityState.any()) {
val ui = previous.ui ?: return@invokeAndWaitIfNeeded val ui = previous.ui ?: return@invokeAndWaitIfNeeded
IdeFocusManager.getInstance(project).requestFocus(ui, true) IdeFocusManager.getInstance(project).requestFocus(ui, true)
} }
GuiTestUtil.shortcut(Key.BACK_SPACE) invokeActionViaShortcut("BACK_SPACE")
type("0") type("0")
} }
} }
@@ -144,11 +141,9 @@ class PythonQuickFixesRefactoringLesson
!stackInsideDialogRefactoring() !stackInsideDialogRefactoring()
} }
test { test(waitEditorToBeReady = false) {
with(TaskTestContext.guiTestCase) { dialog("Change Signature") {
dialog("Change Signature") { button("Refactor").click()
button("Refactor").click()
}
} }
} }
} }

View File

@@ -3,17 +3,12 @@ package com.jetbrains.python.ift.lesson.refactorings
import com.intellij.ide.DataManager import com.intellij.ide.DataManager
import com.intellij.ide.actions.exclusion.ExclusionHandler import com.intellij.ide.actions.exclusion.ExclusionHandler
import com.intellij.openapi.application.runReadAction
import com.intellij.refactoring.RefactoringBundle 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.ui.tree.TreeVisitor
import com.intellij.usageView.UsageViewBundle import com.intellij.usageView.UsageViewBundle
import com.intellij.util.ui.tree.TreeUtil import com.intellij.util.ui.tree.TreeUtil
import com.jetbrains.python.ift.PythonLessonsBundle import com.jetbrains.python.ift.PythonLessonsBundle
import org.fest.swing.fixture.JTreeFixture
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
import training.dsl.* import training.dsl.*
import training.learn.LessonsBundle import training.learn.LessonsBundle
@@ -24,6 +19,7 @@ import javax.swing.JTree
import javax.swing.tree.TreePath import javax.swing.tree.TreePath
class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesson.name")) { class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesson.name")) {
override val testScriptProperties = TaskTestContext.TestScriptProperties(10)
private val template = """ private val template = """
class Championship: class Championship:
def __init__(self): def __init__(self):
@@ -55,9 +51,6 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
print(c.<name>) print(c.<name>)
""".trimIndent() + '\n' """.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 sample = parseLessonSample(template.replace("<name>", "teams"))
private val replacePreviewPattern = Pattern.compile(".*Variable to be renamed to (\\w+).*") 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) prepareSample(sample)
val dynamicWord = UsageViewBundle.message("usage.view.results.node.dynamic") val dynamicWord = UsageViewBundle.message("usage.view.results.node.dynamic")
var replace: String? = null var replace: String? = null
var dynamicItem: String? = null
task("RenameElement") { task("RenameElement") {
text(PythonLessonsBundle.message("python.rename.press.rename", action(it), code("teams"), code("teams_number"))) text(PythonLessonsBundle.message("python.rename.press.rename", action(it), code("teams"), code("teams_number")))
triggerByFoundPathAndHighlight { tree: JTree, path: TreePath -> 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) replace = replacePreviewPattern.matcher(tree.model.root.toString()).takeIf { m -> m.find() }?.group(1)
true true
} }
@@ -77,11 +73,9 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
} }
test { test {
actions(it) actions(it)
with(TaskTestContext.guiTestCase) { dialog {
dialog { type("teams_number")
typeText("teams_number") button("Refactor").click()
button("Refactor").click()
}
} }
} }
} }
@@ -102,12 +96,14 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
} }
showWarningIfFindToolbarClosed() showWarningIfFindToolbarClosed()
test { test {
//TODO: Fix tree access
val jTree = previous.ui as? JTree ?: return@test
val di = dynamicItem ?: return@test
ideFrame { ideFrame {
val jTree = runReadAction { val jTreeFixture = JTreeFixture(robot, jTree)
jTree(dynamicReferencesString, timeout = Timeouts.seconds03, predicate = substringPredicate) jTreeFixture.replaceSeparator("@@@")
} jTreeFixture.expandPath(di)
// WARNING: several exception will be here because of UsageNode#toString inside info output during this operation // 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() showWarningIfFindToolbarClosed()
test { test {
ideFrame { ideFrame {
type("co_me") type("come")
GuiTestUtil.shortcut(Key.DELETE) invokeActionViaShortcut("DELETE")
} }
} }
} }
@@ -149,7 +145,7 @@ class PythonRenameLesson : KLesson("Rename", LessonsBundle.message("rename.lesso
text(PythonLessonsBundle.message("python.rename.finish.refactoring", strong(confirmRefactoringButton))) text(PythonLessonsBundle.message("python.rename.finish.refactoring", strong(confirmRefactoringButton)))
stateCheck { editor.document.text == result } stateCheck { editor.document.text == result }
showWarningIfFindToolbarClosed() showWarningIfFindToolbarClosed()
test { test(waitEditorToBeReady = false) {
ideFrame { ideFrame {
button(confirmRefactoringButton).click() button(confirmRefactoringButton).click()
} }