/*
* 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.intellij.ide.errorTreeView.*;
import com.intellij.openapi.externalSystem.service.notification.EditableNotificationMessageElement;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.ui.content.Content;
import org.fest.swing.core.Robot;
import org.fest.swing.edt.GuiActionRunner;
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.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.fail;
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.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 ContentFixture(myRobot, content);
}
public static class ContentFixture {
@NotNull private final Robot myRobot;
@NotNull private final Content myContent;
private ContentFixture(@NotNull Robot robot, @NotNull Content content) {
myRobot = robot;
myContent = content;
}
@NotNull
public ContentFixture requireMessage(@NotNull ErrorTreeElementKind kind, @NotNull final String... text) {
MessageMatcher equalMatcher = new MessageMatcher() {
@Override
public boolean matches(@NotNull String[] actual) {
return Arrays.equals(text, actual);
}
@Override
public String toString() {
return Arrays.toString(text);
}
};
return requireMessage(kind, equalMatcher);
}
@NotNull
public ContentFixture requireMessage(@NotNull ErrorTreeElementKind kind, @NotNull MessageMatcher matcher) {
doFindMessage(kind, matcher);
return this;
}
@NotNull
public MessageFixture findMessage(@NotNull ErrorTreeElementKind kind, @NotNull MessageMatcher matcher) {
ErrorTreeElement found = doFindMessage(kind, matcher);
return new MessageFixture(myRobot, found);
}
@NotNull
private ErrorTreeElement doFindMessage(@NotNull final ErrorTreeElementKind kind, @NotNull final MessageMatcher matcher) {
ErrorTreeElement found = GuiActionRunner.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);
}
});
if (found == null) {
fail(String.format("Failed to find message of type %1$s and matching text %2$s", kind, matcher.toString()));
}
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) {
return text[0].startsWith(prefix);
}
@Override
public String toString() {
return "first line starting with " + quote(prefix);
}
};
}
}
public static class MessageFixture {
private static final Pattern ANCHOR_TAG_PATTERN = Pattern.compile("<a href=\"(.*?)\">([^<]+)</a>");
@NotNull private final Robot myRobot;
@NotNull private final ErrorTreeElement myTarget;
MessageFixture(@NotNull Robot robot, @NotNull ErrorTreeElement target) {
myRobot = robot;
myTarget = target;
}
@NotNull
public HyperlinkFixture findHyperlink(@NotNull String hyperlinkText) {
// 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);
// Find the URL of the hyperlink.
final EditableNotificationMessageElement message = (EditableNotificationMessageElement)myTarget;
final JEditorPane editorComponent = GuiActionRunner.execute(new GuiQuery<JEditorPane>() {
@Override
protected JEditorPane executeInEDT() throws Throwable {
TreeCellEditor cellEditor = message.getRightSelfEditor();
return field("editorComponent").ofType(JEditorPane.class).in(cellEditor).get();
}
});
String text = GuiActionRunner.execute(new GuiQuery<String>() {
@Override
protected String executeInEDT() throws Throwable {
return editorComponent.getText();
}
});
String url = null;
Matcher matcher = ANCHOR_TAG_PATTERN.matcher(text);
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 new HyperlinkFixture(myRobot, editorComponent, url);
}
}
public static class HyperlinkFixture {
@NotNull private final Robot myRobot;
@NotNull private final JEditorPane myTarget;
@NotNull private final String myUrl;
HyperlinkFixture(@NotNull Robot robot, @NotNull JEditorPane target, @NotNull String url) {
myRobot = robot;
myTarget = target;
myUrl = url;
}
@NotNull
public HyperlinkFixture click() {
// at least move the mouse where the message is, so we can know that something is happening.
myRobot.moveMouse(visibleCenterOf(myTarget));
GuiActionRunner.execute(new GuiTask() {
@Override
protected void executeInEDT() throws Throwable {
myTarget.fireHyperlinkUpdate(new HyperlinkEvent(this, ACTIVATED, null, myUrl));
}
});
return this;
}
@NotNull
public HyperlinkFixture requireUrl(@NotNull String expected) {
assertThat(myUrl).as("URL").isEqualTo(expected);
return this;
}
}
}