blob: a1723265caafe29c254ca971ace64527e5558711 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.tests.gui.framework.fixture;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.intellij.ide.errorTreeView.*;
import com.intellij.openapi.externalSystem.service.notification.EditableNotificationMessageElement;
import com.intellij.openapi.externalSystem.service.notification.NotificationMessageElement;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.pom.Navigatable;
import com.intellij.ui.content.Content;
import org.fest.swing.core.Robot;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.edt.GuiTask;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.tree.TreeCellEditor;
import java.io.File;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import static junit.framework.Assert.assertNotNull;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.reflect.core.Reflection.field;
import static org.fest.swing.awt.AWT.visibleCenterOf;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.util.Strings.quote;
public class MessagesToolWindowFixture extends ToolWindowFixture {
MessagesToolWindowFixture(@NotNull Project project, @NotNull Robot robot) {
super(ToolWindowId.MESSAGES_WINDOW, project, robot);
}
@NotNull
public ContentFixture getGradleSyncContent() {
Content content = getContent("Gradle Sync");
assertNotNull(content);
return new SyncContentFixture(content);
}
@NotNull
public ContentFixture getGradleBuildContent() {
Content content = getContent("Gradle Build");
assertNotNull(content);
return new BuildContentFixture(content);
}
public abstract static class ContentFixture {
@NotNull private final Content myContent;
private ContentFixture(@NotNull Content content) {
myContent = content;
}
@NotNull
public MessageFixture findMessageContainingText(@NotNull ErrorTreeElementKind kind, @NotNull final String text) {
ErrorTreeElement element = doFindMessage(kind, new MessageMatcher() {
@Override
protected boolean matches(@NotNull String[] lines) {
for (String s : lines) {
if (s.contains(text)) {
return true;
}
}
return false;
}
});
return createFixture(element);
}
@NotNull
public MessageFixture findMessage(@NotNull ErrorTreeElementKind kind, @NotNull MessageMatcher matcher) {
ErrorTreeElement found = doFindMessage(kind, matcher);
return createFixture(found);
}
@NotNull
protected abstract MessageFixture createFixture(@NotNull ErrorTreeElement element);
@NotNull
private ErrorTreeElement doFindMessage(@NotNull final ErrorTreeElementKind kind, @NotNull final MessageMatcher matcher) {
ErrorTreeElement found = execute(new GuiQuery<ErrorTreeElement>() {
@Override
@Nullable
protected ErrorTreeElement executeInEDT() throws Throwable {
NewErrorTreeViewPanel component = (NewErrorTreeViewPanel)myContent.getComponent();
ErrorViewStructure errorView = component.getErrorViewStructure();
Object root = errorView.getRootElement();
return findMessage(errorView, errorView.getChildElements(root), matcher, kind);
}
});
assertNotNull(String.format("Failed to find message of type %1$s and matching text %2$s", kind, matcher.toString()), found);
return found;
}
@Nullable
private static ErrorTreeElement findMessage(@NotNull ErrorViewStructure errorView,
@NotNull ErrorTreeElement[] children,
@NotNull MessageMatcher matcher,
@NotNull ErrorTreeElementKind kind) {
for (ErrorTreeElement child : children) {
if (child instanceof GroupingElement) {
ErrorTreeElement found = findMessage(errorView, errorView.getChildElements(child), matcher, kind);
if (found != null) {
return found;
}
}
if (kind == child.getKind() && matcher.matches(child.getText())) {
return child;
}
}
return null;
}
}
public static abstract class MessageMatcher {
protected abstract boolean matches(@NotNull String[] text);
@NotNull
public static MessageMatcher firstLineStartingWith(@NotNull final String prefix) {
return new MessageMatcher() {
@Override
public boolean matches(@NotNull String[] text) {
assertThat(text).isNotEmpty();
return text[0].startsWith(prefix);
}
@Override
public String toString() {
return "first line starting with " + quote(prefix);
}
};
}
}
public class SyncContentFixture extends ContentFixture {
SyncContentFixture(@NotNull Content content) {
super(content);
}
@Override
@NotNull
protected MessageFixture createFixture(@NotNull ErrorTreeElement element) {
return new SyncMessageFixture(myRobot, element);
}
}
public class BuildContentFixture extends ContentFixture {
BuildContentFixture(@NotNull Content content) {
super(content);
}
@Override
@NotNull
protected MessageFixture createFixture(@NotNull ErrorTreeElement element) {
throw new UnsupportedOperationException();
}
}
public abstract static class MessageFixture {
private static final Pattern ANCHOR_TAG_PATTERN = Pattern.compile("<a href=\"(.*?)\">([^<]+)</a>");
@NotNull protected final Robot myRobot;
@NotNull protected final ErrorTreeElement myTarget;
protected MessageFixture(@NotNull Robot robot, @NotNull ErrorTreeElement target) {
myRobot = robot;
myTarget = target;
}
@NotNull
public abstract HyperlinkFixture findHyperlink(@NotNull String hyperlinkText);
@NotNull
protected String extractUrl(@NotNull String wholeText, @NotNull String hyperlinkText) {
String url = null;
Matcher matcher = ANCHOR_TAG_PATTERN.matcher(wholeText);
while (matcher.find()) {
String anchorText = matcher.group(2);
// Text may be spread across multiple lines. Put everything in one line.
if (anchorText != null) {
anchorText = anchorText.replaceAll("[\\s]+", " ");
if (anchorText.equals(hyperlinkText)) {
url = matcher.group(1);
break;
}
}
}
assertNotNull("Failed to find URL for hyperlink " + quote(hyperlinkText), url);
return url;
}
@NotNull
public MessageFixture requireLocation(@NotNull File filePath, int line) {
doRequireLocation(filePath, line);
return this;
}
protected void doRequireLocation(@NotNull File expectedFilePath, int line) {
assertThat(myTarget).isInstanceOf(NotificationMessageElement.class);
NotificationMessageElement element = (NotificationMessageElement)myTarget;
Navigatable navigatable = element.getNavigatable();
assertThat(navigatable).isInstanceOf(OpenFileDescriptor.class);
OpenFileDescriptor descriptor = (OpenFileDescriptor)navigatable;
File actualFilePath = virtualToIoFile(descriptor.getFile());
assertThat(actualFilePath).isEqualTo(expectedFilePath);
assertThat((descriptor.getLine() + 1)).as("line").isEqualTo(line); // descriptor line is zero-based.
}
@NotNull
public abstract String getText();
}
public static class SyncMessageFixture extends MessageFixture {
SyncMessageFixture(@NotNull Robot robot, @NotNull ErrorTreeElement target) {
super(robot, target);
}
@Override
@NotNull
public HyperlinkFixture findHyperlink(@NotNull String hyperlinkText) {
Pair<JEditorPane, String> cellEditorAndText = getCellEditorAndText();
String url = extractUrl(cellEditorAndText.getSecond(), hyperlinkText);
return new SyncHyperlinkFixture(myRobot, url, cellEditorAndText.getFirst());
}
@Override
@NotNull
public String getText() {
String html = getCellEditorAndText().getSecond();
int startBodyIndex = html.indexOf("<body>");
assertThat(startBodyIndex).isGreaterThanOrEqualTo(0);
int endBodyIndex = html.indexOf("</body>");
assertThat(endBodyIndex).isGreaterThan(startBodyIndex);
String body = html.substring(startBodyIndex + 6 /* 6 = length of '<body>' */, endBodyIndex);
List<String> lines = Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(body);
body = Joiner.on(' ').join(lines);
return body;
}
@NotNull
private Pair<JEditorPane, String> getCellEditorAndText() {
// There is no specific UI component for a hyperlink in the "Messages" window. Instead we have a JEditorPane with HTML. This method
// finds the anchor tags, and matches the text of each of them against the given text. If a matching hyperlink is found, we fire a
// HyperlinkEvent, simulating a click on the actual hyperlink.
assertThat(myTarget).isInstanceOf(EditableNotificationMessageElement.class);
final JEditorPane editorComponent = execute(new GuiQuery<JEditorPane>() {
@Override
protected JEditorPane executeInEDT() throws Throwable {
EditableNotificationMessageElement message = (EditableNotificationMessageElement)myTarget;
TreeCellEditor cellEditor = message.getRightSelfEditor();
return field("editorComponent").ofType(JEditorPane.class).in(cellEditor).get();
}
});
assertNotNull(editorComponent);
String text = execute(new GuiQuery<String>() {
@Override
protected String executeInEDT() throws Throwable {
return editorComponent.getText();
}
});
assertNotNull(text);
return Pair.create(editorComponent, text);
}
}
public abstract static class HyperlinkFixture {
@NotNull protected final Robot myRobot;
@NotNull protected final String myUrl;
protected HyperlinkFixture(@NotNull Robot robot, @NotNull String url) {
myRobot = robot;
myUrl = url;
}
@NotNull
public HyperlinkFixture requireUrl(@NotNull String expected) {
assertThat(myUrl).as("URL").isEqualTo(expected);
return this;
}
@NotNull
public HyperlinkFixture click() {
click(true);
return this;
}
/**
* Simulates a click on the hyperlink. This method returns immediately and does not wait for any UI actions triggered by the click to be
* finished.
*/
public HyperlinkFixture clickAndContinue() {
click(false);
return this;
}
private void click(boolean synchronous) {
if (synchronous) {
execute(new GuiTask() {
@Override
protected void executeInEDT() {
new Runnable() {
@Override
public void run() {
doClick();
}
}.run();
}
});
}
else {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
doClick();
}
});
}
}
protected abstract void doClick();
}
public static class SyncHyperlinkFixture extends HyperlinkFixture {
@NotNull private final JEditorPane myTarget;
SyncHyperlinkFixture(@NotNull Robot robot, @NotNull String url, @NotNull JEditorPane target) {
super(robot, url);
myTarget = target;
}
@Override
protected void doClick() {
// at least move the mouse where the message is, so we can know that something is happening.
myRobot.moveMouse(visibleCenterOf(myTarget));
myTarget.fireHyperlinkUpdate(new HyperlinkEvent(this, ACTIVATED, null, myUrl));
}
}
}