GUI-67 Can't record actions in mocha run tool window (continue)

Separate file based EditorFixture
This commit is contained in:
Maxim.Kolmakov
2018-01-12 16:38:13 +01:00
parent 0d587dd40c
commit 473018708d
4 changed files with 338 additions and 310 deletions
@@ -15,19 +15,13 @@
*/
package com.intellij.testGuiFramework.fixtures;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.impl.EditorComponentImpl;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import org.fest.swing.core.GenericTypeMatcher;
@@ -55,12 +49,9 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.testGuiFramework.framework.GuiTestUtil.SHORT_TIMEOUT;
import static com.intellij.testGuiFramework.framework.GuiTestUtil.THIRTY_SEC_TIMEOUT;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.reflect.core.Reflection.method;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.timing.Pause.pause;
import static org.fest.util.Strings.quote;
import static org.junit.Assert.*;
/**
@@ -83,18 +74,6 @@ public class EditorFixture {
private final IdeFrameFixture myFrame;
private final EditorTabsFixture tabs;
private final Editor myEditor;
private final FileEditorManager myManager;
/**
* Constructs a new editor fixture, tied to the given project
*/
public EditorFixture(Robot robot, IdeFrameFixture frame) {
this.robot = robot;
myFrame = frame;
tabs = new EditorTabsFixture(robot, frame);
myEditor = null;
myManager = FileEditorManager.getInstance(myFrame.getProject());
}
/**
* Constructs a new editor fixture, tied to the given project
@@ -104,44 +83,6 @@ public class EditorFixture {
myEditor = editor;
myFrame = null;
tabs = null;
myManager = FileEditorManager.getInstance(editor.getProject());
}
/**
* Returns the current file being shown in the editor, if there is a current
* editor open and it's a file editor
*
* @return the currently edited file or null
*/
@Nullable
public VirtualFile getCurrentFile() {
VirtualFile[] selectedFiles = myManager.getSelectedFiles();
if (selectedFiles.length > 0) {
// we should be sure that EditorComponent is already showing
VirtualFile selectedFile = selectedFiles[0];
if (myManager.getEditors(selectedFile).length == 0) {
return null;
}
else {
FileEditor editor = myManager.getEditors(selectedFile)[0];
return editor.getComponent().isShowing() ? selectedFile : null;
}
}
return null;
}
/**
* Returns the name of the current file, if any. Convenience method
* for {@link #getCurrentFile()}.getName().
*
* @return the current file name, or null
*/
@Nullable
public String getCurrentFileName() {
VirtualFile currentFile = getCurrentFile();
return currentFile != null ? currentFile.getName() : null;
}
/**
@@ -266,8 +207,7 @@ public class EditorFixture {
}
private Editor getEditor() {
if(myEditor != null) return myEditor;
else return myManager.getSelectedTextEditor();
return myEditor;
}
/**
@@ -345,7 +285,7 @@ public class EditorFixture {
* Returns content of the current editor's document with a given range.
*
* @param startOffset the content which starting right after this offset
* @param endOffset the end of content, symbol on the endOffset will be not included
* @param endOffset the end of content, symbol on the endOffset will be not included
* @return
*/
public String getCurrentFileContents(int startOffset, int endOffset) {
@@ -648,10 +588,12 @@ public class EditorFixture {
String contents = editor.getDocument().getCharsSequence().toString();
Matcher matcher = Pattern.compile(regex).matcher(contents);
if (matcher.find()) {
if (returnStartIndex)
if (returnStartIndex) {
return matcher.start();
else
}
else {
return matcher.end();
}
}
}
return -1;
@@ -686,29 +628,6 @@ public class EditorFixture {
return findOffset(prefix, suffix, true);
}
/**
* Selects the given tab in the current editor. Used to switch between
* design mode and editor mode for example.
*
* @param tab the tab to switch to
*/
public EditorFixture selectEditorView(@NotNull final Tab tab) {
switch (tab) {
case EDITOR:
selectEditorView("Text");
break;
case DESIGN:
selectEditorView("Design");
break;
case DEFAULT:
selectEditorView((String)null);
break;
default:
fail("Unknown tab " + tab);
}
return this;
}
/**
* Selects the editor with a given tab name.
@@ -730,107 +649,6 @@ public class EditorFixture {
return tabs.hasTab(tabName);
}
/**
* Selects the given tab in the current editor. Used to switch between
* design mode and editor mode for example.
*
* @param tabName the label in the editor, or null for the default (first) tab
*/
public EditorFixture selectEditorView(@Nullable final String tabName) {
execute(new GuiTask() {
@Override
protected void executeInEDT() {
VirtualFile currentFile = getCurrentFile();
assertNotNull("Can't switch to tab " + tabName + " when no file is open in the editor", currentFile);
FileEditor[] editors = myManager.getAllEditors(currentFile);
FileEditor target = null;
for (FileEditor editor : editors) {
if (tabName == null || tabName.equals(editor.getName())) {
target = editor;
break;
}
}
if (target != null) {
// Have to use reflection
//FileEditorManagerImpl#setSelectedEditor(final FileEditor editor)
method("setSelectedEditor").withParameterTypes(FileEditor.class).in(myManager).invoke(target);
return;
}
List<String> tabNames = new ArrayList<>();
for (FileEditor editor : editors) {
tabNames.add(editor.getName());
}
fail("Could not find editor tab \"" + (tabName != null ? tabName : "<default>") + "\": Available tabs = " + tabNames);
}
});
return this;
}
/**
* Opens up a different file. This will run through the "Open File..." dialog to
* find and select the given file.
*
* @param file the file to open
* @param tab which tab to open initially, if there are multiple editors
*/
public EditorFixture open(@NotNull final VirtualFile file, @NotNull final Tab tab) {
execute(new GuiTask() {
@Override
protected void executeInEDT() {
// TODO: Use UI to navigate to the file instead
Project project = myFrame.getProject();
if (tab == Tab.EDITOR) {
myManager.openTextEditor(new OpenFileDescriptor(project, file), true);
}
else {
myManager.openFile(file, true);
}
}
});
pause(new Condition("File " + quote(file.getPath()) + " to be opened") {
@Override
public boolean test() {
//noinspection ConstantConditions
return execute(new GuiQuery<Boolean>() {
@Override
protected Boolean executeInEDT() {
FileEditor[] editors = FileEditorManager.getInstance(myFrame.getProject()).getEditors(file);
if (editors.length == 0) return false;
return editors[0].getComponent().isShowing();
}
});
}
}, SHORT_TIMEOUT);
// TODO: Maybe find a better way to keep Documents in sync with their VirtualFiles.
invokeActionViaKeystroke("Synchronize");
return this;
}
/**
* Opens up a different file. This will run through the "Open File..." dialog to
* find and select the given file.
*
* @param file the project-relative path (with /, not File.separator, as the path separator)
* @param tab which tab to open initially, if there are multiple editors
*/
public EditorFixture open(@NotNull final String relativePath, @NotNull Tab tab) {
assertFalse("Should use '/' in test relative paths, not File.separator", relativePath.contains("\\"));
VirtualFile file = myFrame.findFileByRelativePath(relativePath, true);
return open(file, tab);
}
/**
* Like {@link #open(String, com.android.tools.idea.tests.gui.framework.fixture.EditorFixture.Tab)} but
* always uses the default tab
*
* @param file the project-relative path (with /, not File.separator, as the path separator)
*/
public EditorFixture open(@NotNull final String relativePath) {
return open(relativePath, Tab.DEFAULT);
}
/**
* Invokes the given action. This will look up the corresponding action's key bindings, if any, and invoke
@@ -947,7 +765,7 @@ public class EditorFixture {
return this;
}
private void invokeActionViaKeystroke(@NotNull String actionId) {
protected void invokeActionViaKeystroke(@NotNull String actionId) {
AnAction action = ActionManager.getInstance().getAction(actionId);
assertNotNull(actionId, action);
assertTrue(actionId + " is not enabled", action.getTemplatePresentation().isEnabled());
@@ -984,84 +802,6 @@ public class EditorFixture {
}
}
/**
* Checks that the editor has a given number of issues. This is a convenience wrapper
* for {@link FileFixture#requireCodeAnalysisHighlightCount(HighlightSeverity, int)}
*
* @param severity the severity of the issues you want to count
* @param expected the expected count
* @return this
*/
@NotNull
public EditorFixture requireCodeAnalysisHighlightCount(@NotNull HighlightSeverity severity, int expected) {
FileFixture file = getCurrentFileFixture();
file.requireCodeAnalysisHighlightCount(severity, expected);
return this;
}
@NotNull
public EditorFixture requireHighlights(HighlightSeverity severity, String... highlights) {
List<String> infos = new ArrayList<>();
for (HighlightInfo info : getCurrentFileFixture().getHighlightInfos(severity)) {
infos.add(info.getDescription());
}
assertThat(infos).containsOnly((Object[])highlights);
return this;
}
/**
* Waits until the editor has the given number of errors at the given severity.
* Typically used when you want to invoke an intention action, but need to wait until
* the code analyzer has found an error it needs to resolve first.
*
* @param severity the severity of the issues you want to count
* @param expected the expected count
* @return this
*/
@NotNull
public EditorFixture waitForCodeAnalysisHighlightCount(@NotNull final HighlightSeverity severity, int expected) {
FileFixture file = getCurrentFileFixture();
file.waitForCodeAnalysisHighlightCount(severity, expected);
return this;
}
@NotNull
public EditorFixture waitUntilErrorAnalysisFinishes() {
FileFixture file = getCurrentFileFixture();
file.waitUntilErrorAnalysisFinishes();
return this;
}
/**
* An Editor could load files async, sometimes we should wait a bit when the virtual
* file for a current editor will be set.
*
* @return FileFixture for loaded virtual file
*/
@NotNull
public FileFixture waitUntilFileIsLoaded() {
Ref<VirtualFile> virtualFileReference = new Ref<>();
pause(new Condition("Wait when virtual file is created...") {
@Override
public boolean test() {
virtualFileReference.set(execute(new GuiQuery<VirtualFile>() {
@Override
protected VirtualFile executeInEDT() {
return getCurrentFile();
}
}));
return virtualFileReference.get() != null;
}
}, THIRTY_SEC_TIMEOUT);
return new FileFixture(myFrame.getProject(), virtualFileReference.get());
}
@NotNull
private FileFixture getCurrentFileFixture() {
VirtualFile currentFile = getCurrentFile();
assertNotNull("Expected a file to be open", currentFile);
return new FileFixture(myFrame.getProject(), currentFile);
}
/**
* Invokes the show intentions action, waits for the actions to be displayed and then picks the
@@ -1073,7 +813,7 @@ public class EditorFixture {
@NotNull
public EditorFixture invokeIntentionAction(@NotNull String labelPrefix) {
invokeAction(EditorFixture.EditorAction.SHOW_INTENTION_ACTIONS);
JBListPopupFixture.clickPopupMenuItem(labelPrefix, true,null, robot, SHORT_TIMEOUT);
JBListPopupFixture.clickPopupMenuItem(labelPrefix, true, null, robot, SHORT_TIMEOUT);
return this;
}
@@ -1174,44 +914,6 @@ public class EditorFixture {
//
// return new ThemeEditorFixture(robot, themeEditorComponent);
//}
/**
* Requires the source editor's current file name to be the given name (or if null, for there
* to be no current file)
*/
public void requireName(@Nullable String name) {
VirtualFile currentFile = getCurrentFile();
if (name == null) {
assertNull("Expected editor to not have an open file, but is showing " + currentFile, currentFile);
}
else if (currentFile == null) {
fail("Expected file " + name + " to be showing, but the editor is not showing anything");
}
else {
assertEquals(name, currentFile.getName());
}
}
/**
* Requires the source editor's current file to be in the given folder (or if null, for there
* to be no current file)
*/
public void requireFolderName(@Nullable String name) {
VirtualFile currentFile = getCurrentFile();
if (name == null) {
assertNull("Expected editor to not have an open file, but is showing " + currentFile, currentFile);
}
else if (currentFile == null) {
fail("Expected file " + name + " to be showing, but the editor is not showing anything");
}
else {
VirtualFile parent = currentFile.getParent();
assertNotNull("File " + currentFile.getName() + " does not have a parent", parent);
assertEquals(name, parent.getName());
}
}
public EditorNotificationPanelFixture notificationPanel() {
return EditorNotificationPanelFixture.Companion.findEditorNotificationPanel(robot, 30);
}
@@ -0,0 +1,326 @@
/*
* Copyright 2000-2018 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.intellij.testGuiFramework.fixtures;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import org.fest.swing.core.Robot;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.edt.GuiTask;
import org.fest.swing.timing.Condition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.testGuiFramework.framework.GuiTestUtil.SHORT_TIMEOUT;
import static com.intellij.testGuiFramework.framework.GuiTestUtil.THIRTY_SEC_TIMEOUT;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.reflect.core.Reflection.method;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.timing.Pause.pause;
import static org.fest.util.Strings.quote;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class FileEditorFixture extends EditorFixture {
private final FileEditorManager myManager;
private final IdeFrameFixture myFrame;
public FileEditorFixture(Robot robot, IdeFrameFixture frame) {
super(robot, FileEditorManager.getInstance(frame.getProject()).getSelectedTextEditor());
myManager = FileEditorManager.getInstance(frame.getProject());
myFrame = frame;
}
/**
* Returns the current file being shown in the editor, if there is a current
* editor open and it's a file editor
*
* @return the currently edited file or null
*/
@Nullable
public VirtualFile getCurrentFile() {
VirtualFile[] selectedFiles = myManager.getSelectedFiles();
if (selectedFiles.length > 0) {
// we should be sure that EditorComponent is already showing
VirtualFile selectedFile = selectedFiles[0];
if (myManager.getEditors(selectedFile).length == 0) {
return null;
}
else {
FileEditor editor = myManager.getEditors(selectedFile)[0];
return editor.getComponent().isShowing() ? selectedFile : null;
}
}
return null;
}
/**
* Returns the name of the current file, if any. Convenience method
* for {@link #getCurrentFile()}.getName().
*
* @return the current file name, or null
*/
@Nullable
public String getCurrentFileName() {
VirtualFile currentFile = getCurrentFile();
return currentFile != null ? currentFile.getName() : null;
}
/**
* Selects the given tab in the current editor. Used to switch between
* design mode and editor mode for example.
*
* @param tabName the label in the editor, or null for the default (first) tab
*/
public EditorFixture selectEditorView(@Nullable final String tabName) {
execute(new GuiTask() {
@Override
protected void executeInEDT() {
VirtualFile currentFile = getCurrentFile();
assertNotNull("Can't switch to tab " + tabName + " when no file is open in the editor", currentFile);
FileEditor[] editors = myManager.getAllEditors(currentFile);
FileEditor target = null;
for (FileEditor editor : editors) {
if (tabName == null || tabName.equals(editor.getName())) {
target = editor;
break;
}
}
if (target != null) {
// Have to use reflection
//FileEditorManagerImpl#setSelectedEditor(final FileEditor editor)
method("setSelectedEditor").withParameterTypes(FileEditor.class).in(myManager).invoke(target);
return;
}
List<String> tabNames = new ArrayList<>();
for (FileEditor editor : editors) {
tabNames.add(editor.getName());
}
fail("Could not find editor tab \"" + (tabName != null ? tabName : "<default>") + "\": Available tabs = " + tabNames);
}
});
return this;
}
/**
* Opens up a different file. This will run through the "Open File..." dialog to
* find and select the given file.
*
* @param file the file to open
* @param tab which tab to open initially, if there are multiple editors
*/
public EditorFixture open(@NotNull final VirtualFile file, @NotNull final Tab tab) {
execute(new GuiTask() {
@Override
protected void executeInEDT() {
// TODO: Use UI to navigate to the file instead
Project project = myFrame.getProject();
if (tab == Tab.EDITOR) {
myManager.openTextEditor(new OpenFileDescriptor(project, file), true);
}
else {
myManager.openFile(file, true);
}
}
});
pause(new Condition("File " + quote(file.getPath()) + " to be opened") {
@Override
public boolean test() {
//noinspection ConstantConditions
return execute(new GuiQuery<Boolean>() {
@Override
protected Boolean executeInEDT() {
FileEditor[] editors = FileEditorManager.getInstance(myFrame.getProject()).getEditors(file);
if (editors.length == 0) return false;
return editors[0].getComponent().isShowing();
}
});
}
}, SHORT_TIMEOUT);
// TODO: Maybe find a better way to keep Documents in sync with their VirtualFiles.
invokeActionViaKeystroke("Synchronize");
return this;
}
/**
* Opens up a different file. This will run through the "Open File..." dialog to
* find and select the given file.
*
* @param file the project-relative path (with /, not File.separator, as the path separator)
* @param tab which tab to open initially, if there are multiple editors
*/
public EditorFixture open(@NotNull final String relativePath, @NotNull Tab tab) {
assertFalse("Should use '/' in test relative paths, not File.separator", relativePath.contains("\\"));
VirtualFile file = myFrame.findFileByRelativePath(relativePath, true);
return open(file, tab);
}
/**
* Selects the given tab in the current editor. Used to switch between
* design mode and editor mode for example.
*
* @param tab the tab to switch to
*/
public EditorFixture selectEditorView(@NotNull final Tab tab) {
switch (tab) {
case EDITOR:
selectEditorView("Text");
break;
case DESIGN:
selectEditorView("Design");
break;
case DEFAULT:
selectEditorView((String)null);
break;
default:
fail("Unknown tab " + tab);
}
return this;
}
/**
* Like {@link #open(String, com.android.tools.idea.tests.gui.framework.fixture.EditorFixture.Tab)} but
* always uses the default tab
*
* @param file the project-relative path (with /, not File.separator, as the path separator)
*/
public EditorFixture open(@NotNull final String relativePath) {
return open(relativePath, Tab.DEFAULT);
}
@NotNull
private FileFixture getCurrentFileFixture() {
VirtualFile currentFile = getCurrentFile();
assertNotNull("Expected a file to be open", currentFile);
return new FileFixture(myFrame.getProject(), currentFile);
}
/**
* Waits until the editor has the given number of errors at the given severity.
* Typically used when you want to invoke an intention action, but need to wait until
* the code analyzer has found an error it needs to resolve first.
*
* @param severity the severity of the issues you want to count
* @param expected the expected count
* @return this
*/
@NotNull
public EditorFixture waitForCodeAnalysisHighlightCount(@NotNull final HighlightSeverity severity, int expected) {
FileFixture file = getCurrentFileFixture();
file.waitForCodeAnalysisHighlightCount(severity, expected);
return this;
}
@NotNull
public EditorFixture waitUntilErrorAnalysisFinishes() {
FileFixture file = getCurrentFileFixture();
file.waitUntilErrorAnalysisFinishes();
return this;
}
/**
* An Editor could load files async, sometimes we should wait a bit when the virtual
* file for a current editor will be set.
*
* @return FileFixture for loaded virtual file
*/
@NotNull
public FileFixture waitUntilFileIsLoaded() {
Ref<VirtualFile> virtualFileReference = new Ref<>();
pause(new Condition("Wait when virtual file is created...") {
@Override
public boolean test() {
virtualFileReference.set(execute(new GuiQuery<VirtualFile>() {
@Override
protected VirtualFile executeInEDT() {
return getCurrentFile();
}
}));
return virtualFileReference.get() != null;
}
}, THIRTY_SEC_TIMEOUT);
return new FileFixture(myFrame.getProject(), virtualFileReference.get());
}
/**
* Checks that the editor has a given number of issues. This is a convenience wrapper
* for {@link FileFixture#requireCodeAnalysisHighlightCount(HighlightSeverity, int)}
*
* @param severity the severity of the issues you want to count
* @param expected the expected count
* @return this
*/
@NotNull
public EditorFixture requireCodeAnalysisHighlightCount(@NotNull HighlightSeverity severity, int expected) {
FileFixture file = getCurrentFileFixture();
file.requireCodeAnalysisHighlightCount(severity, expected);
return this;
}
@NotNull
public EditorFixture requireHighlights(HighlightSeverity severity, String... highlights) {
List<String> infos = new ArrayList<>();
for (HighlightInfo info : getCurrentFileFixture().getHighlightInfos(severity)) {
infos.add(info.getDescription());
}
assertThat(infos).containsOnly((Object[])highlights);
return this;
}
/**
* Requires the source editor's current file name to be the given name (or if null, for there
* to be no current file)
*/
public void requireName(@Nullable String name) {
VirtualFile currentFile = getCurrentFile();
if (name == null) {
assertNull("Expected editor to not have an open file, but is showing " + currentFile, currentFile);
}
else if (currentFile == null) {
fail("Expected file " + name + " to be showing, but the editor is not showing anything");
}
else {
assertEquals(name, currentFile.getName());
}
}
/**
* Requires the source editor's current file to be in the given folder (or if null, for there
* to be no current file)
*/
public void requireFolderName(@Nullable String name) {
VirtualFile currentFile = getCurrentFile();
if (name == null) {
assertNull("Expected editor to not have an open file, but is showing " + currentFile, currentFile);
}
else if (currentFile == null) {
fail("Expected file " + name + " to be showing, but the editor is not showing anything");
}
else {
VirtualFile parent = currentFile.getParent();
assertNotNull("File " + currentFile.getName() + " does not have a parent", parent);
assertEquals(name, parent.getName());
}
}
}
@@ -84,7 +84,7 @@ import static org.junit.Assert.assertNull;
public class IdeFrameFixture extends ComponentFixture<IdeFrameFixture, IdeFrameImpl> {
@NotNull private final File myProjectPath;
private EditorFixture myEditor;
private FileEditorFixture myEditor;
private MainToolbarFixture myToolbar;
private NavigationBarFixture myNavBar;
@@ -217,9 +217,9 @@ public class IdeFrameFixture extends ComponentFixture<IdeFrameFixture, IdeFrameI
}
@NotNull
public EditorFixture getEditor() {
public FileEditorFixture getEditor() {
if (myEditor == null) {
myEditor = new EditorFixture(robot(), this);
myEditor = new FileEditorFixture(robot(), this);
}
return myEditor;
@@ -564,7 +564,7 @@ open class GuiTestCase {
* Context function for IdeFrame: get current editor and create EditorFixture instance as a receiver object. Code block after
* it call methods on the receiver object (EditorFixture instance).
*/
fun IdeFrameFixture.editor(func: EditorFixture.() -> Unit) {
fun IdeFrameFixture.editor(func: FileEditorFixture.() -> Unit) {
func(this.editor)
}