diff --git a/.idea/libraries/jsr305_1_3_9.xml b/.idea/libraries/jsr305_1_3_9.xml
new file mode 100644
index 000000000000..af2783af9f2e
--- /dev/null
+++ b/.idea/libraries/jsr305_1_3_9.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/community-tests/community-tests.iml b/community-tests/community-tests.iml
index 142051034725..198696e4de5c 100644
--- a/community-tests/community-tests.iml
+++ b/community-tests/community-tests.iml
@@ -7,5 +7,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/community-tests/src/com/intellij/tests/gui/NewProjectTest.java b/community-tests/src/com/intellij/tests/gui/NewProjectTest.java
new file mode 100644
index 000000000000..c7ead4db7c1a
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/NewProjectTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * 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.intellij.tests.gui;
+
+import com.intellij.tests.gui.fixtures.newProjectWizard.NewProjectWizardFixture;
+import com.intellij.tests.gui.framework.GuiTestCase;
+import com.intellij.tests.gui.framework.IdeGuiTest;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import java.io.IOException;
+
+
+/**
+ * Created by karashevich on 27/05/16.
+ */
+public class NewProjectTest extends GuiTestCase {
+
+ @Test @IdeGuiTest
+ public void testNewProject() throws IOException, InterruptedException {
+ String myName = "TestProject";
+ String myDomain = "com.test";
+ String myPkg = "com.android.test.app";
+
+
+ findWelcomeFrame().createNewProject();
+ NewProjectWizardFixture newProjectWizard = findNewProjectWizard();
+ newProjectWizard.selectProjectType("Java");
+ //newProjectWizard.
+
+ Thread.sleep(10000);
+
+ //newProjectWizard.clickNext()
+ //
+ //newProjectWizard.getConfigureFormFactorStep().selectMinimumSdkApi(MOBILE, myMinSdk);
+ //newProjectWizard.clickNext();
+
+ // Skip "Add Activity" step
+ newProjectWizard.clickNext();
+
+
+ //newProjectWizard.getChooseOptionsForNewFileStep().enterActivityName(myActivity);ё
+ //newProjectWizard.clickFinish();
+
+ }
+
+ @NotNull
+ NewProjectDescriptor newProject(@NotNull String name) {
+ return new NewProjectDescriptor(name);
+ }
+
+ private class NewProjectDescriptor {
+ private String myActivity = "MainActivity";
+ private String myPkg = "com.android.test.app";
+ private String myMinSdk = "19";
+ private String myName = "TestProject";
+ private String myDomain = "com.android";
+ private boolean myWaitForSync = true;
+
+
+ private NewProjectDescriptor(@NotNull String name) {
+ withName(name);
+ }
+
+ /**
+ * Set a custom package to use in the new project
+ */
+ NewProjectDescriptor withPackageName(@NotNull String pkg) {
+ myPkg = pkg;
+ return this;
+ }
+
+ /**
+ * Set a new project name to use for the new project
+ */
+ NewProjectDescriptor withName(@NotNull String name) {
+ myName = name;
+ return this;
+ }
+
+ /**
+ * Set a custom activity name to use in the new project
+ */
+ NewProjectDescriptor withActivity(@NotNull String activity) {
+ myActivity = activity;
+ return this;
+ }
+
+ /**
+ * Set a custom minimum SDK version to use in the new project
+ */
+ NewProjectDescriptor withMinSdk(@NotNull String minSdk) {
+ myMinSdk = minSdk;
+ return this;
+ }
+
+ /**
+ * Set a custom company domain to enter in the new project wizard
+ */
+ NewProjectDescriptor withCompanyDomain(@NotNull String domain) {
+ myDomain = domain;
+ return this;
+ }
+
+ /**
+ * Picks brief names in order to make the test execute faster (less slow typing in name text fields)
+ */
+ NewProjectDescriptor withBriefNames() {
+ withActivity("A").withCompanyDomain("C").withName("P").withPackageName("a.b");
+ return this;
+ }
+
+ /** Turns off the automatic wait-for-sync that normally happens on {@link #create} */
+ NewProjectDescriptor withoutSync() {
+ myWaitForSync = false;
+ return this;
+ }
+
+ /**
+ * Creates a project fixture for this description
+ */
+ void create() {
+ findWelcomeFrame().createNewProject();
+
+ NewProjectWizardFixture newProjectWizard = findNewProjectWizard();
+
+ newProjectWizard.clickNext();
+
+ newProjectWizard.clickNext();
+
+ // Skip "Add Activity" step
+ newProjectWizard.clickNext();
+
+ newProjectWizard.getChooseOptionsForNewFileStep().enterActivityName(myActivity);
+ newProjectWizard.clickFinish();
+
+ }
+ }
+
+
+}
diff --git a/community-tests/src/com/intellij/tests/gui/driver/SearchTextFieldDriver.java b/community-tests/src/com/intellij/tests/gui/driver/SearchTextFieldDriver.java
new file mode 100644
index 000000000000..1baf254f661e
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/driver/SearchTextFieldDriver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.driver;
+
+import com.intellij.ui.SearchTextField;
+import org.fest.swing.annotation.RunsInEDT;
+import org.fest.swing.core.Robot;
+import org.fest.swing.driver.JComponentDriver;
+import org.fest.swing.driver.TextDisplayDriver;
+import org.fest.swing.edt.GuiQuery;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.regex.Pattern;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+
+public class SearchTextFieldDriver extends JComponentDriver implements TextDisplayDriver {
+ public SearchTextFieldDriver(@NotNull Robot robot) {
+ super(robot);
+ }
+
+ @Override
+ @RunsInEDT
+ public void requireText(@NotNull SearchTextField component, String expected) {
+ assertThat(textOf(component)).isEqualTo(expected);
+ }
+
+ @Override
+ @RunsInEDT
+ public void requireText(@NotNull SearchTextField component, final @NotNull Pattern pattern) {
+ assertThat(textOf(component)).matches(pattern.pattern());
+ }
+
+ @Override
+ @RunsInEDT
+ @Nullable
+ public String textOf(final @NotNull SearchTextField component) {
+ return execute(new GuiQuery() {
+ @Override
+ protected
+ @Nullable
+ String executeInEDT() {
+ return component.getText();
+ }
+ });
+ }
+
+ @RunsInEDT
+ public void enterText(@NotNull SearchTextField textBox, @NotNull String text) {
+ focusAndWaitForFocusGain(textBox);
+ robot.enterText(text);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ActionButtonFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ActionButtonFixture.java
new file mode 100644
index 000000000000..8c054dfe65fb
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ActionButtonFixture.java
@@ -0,0 +1,114 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.impl.ActionButton;
+import com.intellij.openapi.util.Ref;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.exception.ComponentLookupException;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.Collection;
+
+import static com.intellij.tests.gui.framework.GuiTests.LONG_TIMEOUT;
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static com.intellij.util.containers.ContainerUtil.getFirstItem;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+import static org.fest.swing.timing.Pause.pause;
+
+public class ActionButtonFixture extends JComponentFixture {
+
+ @NotNull
+ public static ActionButtonFixture findByActionId(@NotNull final String actionId,
+ @NotNull final Robot robot,
+ @NotNull final Container container) {
+ final Ref actionButtonRef = new Ref();
+ pause(new Condition("Find ActionButton with ID '" + actionId + "'") {
+ @Override
+ public boolean test() {
+ Collection found = robot.finder().findAll(container, new GenericTypeMatcher(ActionButton.class) {
+ @Override
+ protected boolean isMatching(@NotNull ActionButton button) {
+ if (button.isVisible()) {
+ AnAction action = button.getAction();
+ if (action != null) {
+ String id = ActionManager.getInstance().getId(action);
+ return actionId.equals(id);
+ }
+ }
+ return false;
+ }
+ });
+ if (found.size() == 1) {
+ actionButtonRef.set(getFirstItem(found));
+ return true;
+ }
+ return false;
+ }
+ }, SHORT_TIMEOUT);
+
+ ActionButton button = actionButtonRef.get();
+ if (button == null) {
+ throw new ComponentLookupException("Failed to find ActionButton with ID '" + actionId + "'");
+ }
+ return new ActionButtonFixture(robot, button);
+ }
+
+ @NotNull
+ public ActionButtonFixture waitUntilEnabledAndShowing() {
+ pause(new Condition("wait for action to be enabled and showing") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Nullable
+ @Override
+ protected Boolean executeInEDT() throws Throwable {
+ ActionButton target = target();
+ if (target.getAction().getTemplatePresentation().isEnabledAndVisible()) {
+ return target.isShowing() && target.isVisible() && target.isEnabled();
+ }
+ return false;
+ }
+ });
+ }
+ }, LONG_TIMEOUT);
+ return this;
+ }
+
+ @NotNull
+ public static ActionButtonFixture findByText(@NotNull final String text, @NotNull Robot robot, @NotNull Container container) {
+ final ActionButton button = robot.finder().find(container, new GenericTypeMatcher(ActionButton.class) {
+ @Override
+ protected boolean isMatching(@NotNull ActionButton button) {
+ AnAction action = button.getAction();
+ return text.equals(action.getTemplatePresentation().getText());
+ }
+ });
+ return new ActionButtonFixture(robot, button);
+ }
+
+ private ActionButtonFixture(@NotNull Robot robot, @NotNull ActionButton target) {
+ super(ActionButtonFixture.class, robot, target);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ActionLinkFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ActionLinkFixture.java
new file mode 100755
index 000000000000..24bbda785a64
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ActionLinkFixture.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import org.fest.swing.core.Robot;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.util.Ref;
+import com.intellij.ui.components.labels.ActionLink;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.exception.ComponentLookupException;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+import java.util.Collection;
+
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static com.intellij.util.containers.ContainerUtil.getFirstItem;
+import static org.fest.swing.timing.Pause.pause;
+
+public class ActionLinkFixture extends JComponentFixture {
+ @NotNull
+ public static ActionLinkFixture findByActionId(@NotNull final String actionId,
+ @NotNull final Robot robot,
+ @NotNull final Container container) {
+ final Ref actionLinkRef = new Ref();
+ pause(new Condition("Find ActionLink with ID '" + actionId + "'") {
+ @Override
+ public boolean test() {
+ Collection found = robot.finder().findAll(container, new GenericTypeMatcher(ActionLink.class) {
+ @Override
+ protected boolean isMatching(@NotNull ActionLink actionLink) {
+ if (actionLink.isVisible()) {
+ AnAction action = actionLink.getAction();
+ String id = ActionManager.getInstance().getId(action);
+ return actionId.equals(id);
+ }
+ return false;
+ }
+ });
+ if (found.size() == 1) {
+ actionLinkRef.set(getFirstItem(found));
+ return true;
+ }
+ return false;
+ }
+ }, SHORT_TIMEOUT);
+
+ ActionLink actionLink = actionLinkRef.get();
+ if (actionLink == null) {
+ throw new ComponentLookupException("Failed to find ActionLink with ID '" + actionId + "'");
+ }
+ return new ActionLinkFixture(robot, actionLink);
+ }
+
+ private ActionLinkFixture(@NotNull Robot robot, @NotNull ActionLink target) {
+ super(ActionLinkFixture.class, robot, target);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ComboBoxActionFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ComboBoxActionFixture.java
new file mode 100644
index 000000000000..ac7ae2836ed2
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ComboBoxActionFixture.java
@@ -0,0 +1,158 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
+import com.intellij.ui.JBListWithHintProvider;
+import com.intellij.ui.popup.PopupFactoryImpl;
+import com.intellij.ui.popup.list.ListPopupModel;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.JButtonFixture;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+import java.awt.*;
+
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+import static org.fest.swing.timing.Pause.pause;
+import static org.junit.Assert.*;
+
+public class ComboBoxActionFixture {
+ @NotNull private Robot myRobot;
+ @NotNull private JButton myTarget;
+ private static final Class> ourComboBoxButtonClass;
+ static {
+ Class> temp = null;
+ try {
+ temp = ComboBoxActionFixture.class.getClassLoader().loadClass(ComboBoxAction.class.getCanonicalName() + "$ComboBoxButton");
+ }
+ catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ ourComboBoxButtonClass = temp;
+ }
+
+ public static ComboBoxActionFixture findComboBox(@NotNull Robot robot, @NotNull Container root) {
+ JButton comboBoxButton = robot.finder().find(root, new GenericTypeMatcher(JButton.class) {
+ @Override
+ protected boolean isMatching(@NotNull JButton component) {
+ return ourComboBoxButtonClass.isInstance(component);
+ }
+ });
+ return new ComboBoxActionFixture(robot, comboBoxButton);
+ }
+
+ public static ComboBoxActionFixture findComboBoxByText(@NotNull Robot robot, @NotNull Container root, @NotNull final String text) {
+ JButton comboBoxButton = robot.finder().find(root, new GenericTypeMatcher(JButton.class) {
+ @Override
+ protected boolean isMatching(@NotNull JButton component) {
+ return ourComboBoxButtonClass.isInstance(component) && component.getText().equals(text);
+ }
+ });
+ return new ComboBoxActionFixture(robot, comboBoxButton);
+ }
+
+ public ComboBoxActionFixture(@NotNull Robot robot, @NotNull JButton target) {
+ myRobot = robot;
+ myTarget = target;
+ }
+
+ public void selectItem(@NotNull String itemName) {
+ click();
+ selectItemByText(getPopupList(), itemName);
+ }
+
+ public String getSelectedItemText() {
+ return execute(new GuiQuery() {
+ @Nullable
+ @Override
+ protected String executeInEDT() throws Throwable {
+ return myTarget.getText();
+ }
+ });
+ }
+
+ private void click() {
+ final JButtonFixture comboBoxButtonFixture = new JButtonFixture(myRobot, myTarget);
+ pause(new Condition("Wait until comboBoxButton is enabled") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ protected Boolean executeInEDT() throws Throwable {
+ return comboBoxButtonFixture.target().isEnabled();
+ }
+ });
+ }
+ }, SHORT_TIMEOUT);
+ comboBoxButtonFixture.click();
+ }
+
+ @NotNull
+ private JList getPopupList() {
+ return myRobot.finder().findByType(JBListWithHintProvider.class);
+ }
+
+ private static void selectItemByText(@NotNull final JList list, @NotNull final String text) {
+ pause(new Condition("Wait until the list is populated.") {
+ @Override
+ public boolean test() {
+ ListPopupModel popupModel = (ListPopupModel)list.getModel();
+ for (int i = 0; i < popupModel.getSize(); ++i) {
+ PopupFactoryImpl.ActionItem actionItem = (PopupFactoryImpl.ActionItem)popupModel.get(i);
+ assertNotNull(actionItem);
+ if (text.equals(actionItem.getText())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }, SHORT_TIMEOUT);
+
+ final Integer appIndex = execute(new GuiQuery() {
+ @Override
+ protected Integer executeInEDT() throws Throwable {
+ ListPopupModel popupModel = (ListPopupModel)list.getModel();
+ for (int i = 0; i < popupModel.getSize(); ++i) {
+ PopupFactoryImpl.ActionItem actionItem = (PopupFactoryImpl.ActionItem)popupModel.get(i);
+ assertNotNull(actionItem);
+ if (text.equals(actionItem.getText())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ });
+ //noinspection ConstantConditions
+ assertTrue(appIndex >= 0);
+
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ list.setSelectedIndex(appIndex);
+ }
+ });
+ assertEquals(text, ((PopupFactoryImpl.ActionItem)list.getSelectedValue()).getText());
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ComponentFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ComponentFixture.java
new file mode 100644
index 000000000000..5cca67e2e075
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ComponentFixture.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * 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.intellij.tests.gui.fixtures;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.driver.ComponentDriver;
+import org.fest.swing.fixture.AbstractComponentFixture;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+
+
+/**
+ * Created by karashevich on 30/05/16.
+ */
+public class ComponentFixture extends AbstractComponentFixture {
+ public ComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @NotNull Class extends C> type) {
+ super(selfType, robot, type);
+ }
+
+ public ComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @Nullable String name, @NotNull Class extends C> type) {
+ super(selfType, robot, name, type);
+ }
+
+ public ComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @NotNull C target) {
+ super(selfType, robot, target);
+ }
+
+ @Override
+ @NotNull
+ protected ComponentDriver createDriver(@NotNull Robot robot) {
+ return new ComponentDriver(robot);
+ }
+}
+
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ConfigureProjectSubsetDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ConfigureProjectSubsetDialogFixture.java
new file mode 100644
index 000000000000..117c98e19f5f
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ConfigureProjectSubsetDialogFixture.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.core.matcher.DialogMatcher;
+import org.fest.swing.fixture.DialogFixture;
+import org.fest.swing.fixture.JTableCellFixture;
+import org.fest.swing.fixture.JTableFixture;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static com.intellij.tests.gui.framework.GuiTests.findAndClickOkButton;
+import static org.fest.swing.core.matcher.DialogMatcher.withTitle;
+import static org.fest.swing.data.TableCell.row;
+import static org.fest.swing.finder.WindowFinder.findDialog;
+
+public class ConfigureProjectSubsetDialogFixture {
+ @NotNull private DialogFixture myDialog;
+ @NotNull private final JTableFixture myModulesTable;
+
+ @NotNull
+ public static ConfigureProjectSubsetDialogFixture find(@NotNull Robot robot) {
+ DialogMatcher matcher = withTitle("Select Modules to Include in Project Subset").andShowing();
+ DialogFixture dialog = findDialog(matcher).withTimeout(SHORT_TIMEOUT.duration()).using(robot);
+ return new ConfigureProjectSubsetDialogFixture(dialog);
+ }
+
+ private ConfigureProjectSubsetDialogFixture(@NotNull DialogFixture dialog) {
+ myDialog = dialog;
+ Robot robot = dialog.robot();
+ myModulesTable = new JTableFixture(robot, robot.finder().findByType(dialog.target(), JTable.class, true));
+ }
+
+ @NotNull
+ public ConfigureProjectSubsetDialogFixture selectModule(@NotNull String moduleName, boolean selected) {
+ JTableCellFixture cell = myModulesTable.cell(moduleName);
+ myModulesTable.enterValue(row(cell.row()).column(0), String.valueOf(selected));
+ return this;
+ }
+
+ public void clickOk() {
+ findAndClickOkButton(myDialog);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/DebugToolWindowFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/DebugToolWindowFixture.java
new file mode 100644
index 000000000000..8902ce1e4da0
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/DebugToolWindowFixture.java
@@ -0,0 +1,24 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import org.jetbrains.annotations.NotNull;
+
+public class DebugToolWindowFixture extends ExecutionToolWindowFixture {
+ public DebugToolWindowFixture(@NotNull IdeFrameFixture frameFixture) {
+ super("Debug", frameFixture);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/EditorFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/EditorFixture.java
new file mode 100644
index 000000000000..c67b5a81894d
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/EditorFixture.java
@@ -0,0 +1,1089 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.google.common.collect.Lists;
+import com.intellij.codeInsight.daemon.impl.HighlightInfo;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.KeyboardShortcut;
+import com.intellij.openapi.actionSystem.Shortcut;
+import com.intellij.openapi.editor.*;
+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.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.components.JBList;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.driver.ComponentDriver;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.fixture.DialogFixture;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.FocusManager;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.InputMethodEvent;
+import java.awt.event.KeyEvent;
+import java.awt.font.TextHitInfo;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.util.List;
+
+import static com.intellij.tests.gui.framework.GuiTests.*;
+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.*;
+
+/**
+ * Fixture wrapping the IDE source editor, providing convenience methods
+ * for controlling the source editor and verifying editor state. Note that unlike
+ * the IntelliJ Editor class, which is one per file, this fixture represents an
+ * editor in the more traditional sense: a container for multiple files, so you
+ * ask "the" editor its current file, to select text in that file, to switch to
+ * a different file, etc.
+ */
+public class EditorFixture {
+ public static final String CARET = "^";
+ public static final String SELECT_BEGIN = "|>";
+ public static final String SELECT_END = "<|";
+
+ /**
+ * Performs simulation of user events on {@link #target}
+ */
+ public final Robot robot;
+ private final IdeFrameFixture myFrame;
+
+ /**
+ * Constructs a new editor fixture, tied to the given project
+ */
+ public EditorFixture(Robot robot, IdeFrameFixture frame) {
+ this.robot = robot;
+ 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() {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ VirtualFile[] selectedFiles = manager.getSelectedFiles();
+ if (selectedFiles.length > 0) {
+ return selectedFiles[0];
+ }
+
+ 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;
+ }
+
+ /**
+ * Returns the line number of the current caret position (0-based).
+ *
+ * @return the current 0-based line number, or -1 if there is no current file
+ */
+ public int getCurrentLineNumber() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected Integer executeInEDT() throws Throwable {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ CaretModel caretModel = editor.getCaretModel();
+ Caret primaryCaret = caretModel.getPrimaryCaret();
+ int offset = primaryCaret.getOffset();
+ Document document = editor.getDocument();
+ return document.getLineNumber(offset);
+ }
+
+ return -1;
+ }
+ });
+ }
+
+ /**
+ * Returns the contents of the current line, or null if there is no
+ * file open. The caret position is indicated by {@code ^}, and
+ * the selection text range, if on the current line, is indicated by
+ * the text inside {@code |> <|}.
+ *
+ * @param trim if true, trim whitespace around the line
+ * @param showPositions if true, show the editor positions (carets, selection)
+ * @param additionalLines 0, or a count for additional number of lines to include on each side of the current line
+ * @return the text contents at the current caret position
+ */
+ @Nullable
+ public String getCurrentLineContents(boolean trim, boolean showPositions, int additionalLines) {
+ if (showPositions) {
+ return getCurrentLineContents(trim, CARET, SELECT_BEGIN, SELECT_END, additionalLines);
+ }
+ else {
+ return getCurrentLineContents(trim, null, null, null, additionalLines);
+ }
+ }
+
+ /**
+ * Returns the contents of the current line, or null if there is no
+ * file open.
+ *
+ * @param trim if true, trim whitespace around the line
+ * @param caretString typically "^" which will insert "^" to indicate the
+ * caret position. If null, the caret position is not shown.
+ * @param selectBegin the text string to insert at the beginning of the selection boundary
+ * @param selectEnd the text string to insert at the end of the selection boundary
+ * @return the text contents at the current caret position
+ */
+ @Nullable
+ public String getCurrentLineContents(final boolean trim,
+ @Nullable final String caret,
+ @Nullable final String selectBegin,
+ @Nullable final String selectEnd,
+ final int additionalLines) {
+ return execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected String executeInEDT() throws Throwable {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ CaretModel caretModel = editor.getCaretModel();
+ Caret primaryCaret = caretModel.getPrimaryCaret();
+ int offset = primaryCaret.getOffset();
+ int start = primaryCaret.getSelectionStart();
+ int end = primaryCaret.getSelectionEnd();
+ if (start == end) {
+ start = -1;
+ end = -1;
+ }
+ Document document = editor.getDocument();
+ int lineNumber = document.getLineNumber(offset);
+ int lineStart = document.getLineStartOffset(lineNumber);
+ int lineEnd = document.getLineEndOffset(lineNumber);
+ int lineCount = document.getLineCount();
+ for (int i = 1; i <= additionalLines; i++) {
+ if (lineNumber - i >= 0) {
+ lineStart = document.getLineStartOffset(lineNumber - i);
+ }
+ if (lineNumber + i < lineCount) {
+ lineEnd = document.getLineEndOffset(lineNumber + i);
+ }
+ }
+
+ String line = document.getText(new TextRange(lineStart, lineEnd));
+ offset -= lineStart;
+ start -= lineStart;
+ end -= lineStart;
+ StringBuilder sb = new StringBuilder(line.length() + 10);
+ for (int i = 0, n = line.length(); i < n; i++) {
+ if (selectBegin != null && start == i) {
+ sb.append(selectBegin);
+ }
+ if (caret != null && offset == i) {
+ sb.append(caret);
+ }
+ sb.append(line.charAt(i));
+ if (selectEnd != null && end == i + 1) {
+ sb.append(selectEnd);
+ }
+ }
+ String result = sb.toString();
+ if (trim) {
+ result = result.trim();
+ }
+ return result;
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Returns the contents of the current file, or null if there is no
+ * file open. The caret position is indicated by {@code ^}, and
+ * the selection text range, if on the current line, is indicated by
+ * the text inside {@code |> <|}.
+ *
+ * @param showPositions if true, show the editor positions (carets, selection)
+ * @return the text contents at the current caret position
+ */
+ @Nullable
+ public String getCurrentFileContents(boolean showPositions) {
+ if (showPositions) {
+ return getCurrentFileContents(CARET, SELECT_BEGIN, SELECT_END);
+ }
+ else {
+ return getCurrentFileContents(null, null, null);
+ }
+ }
+
+ /**
+ * Returns the contents of the current file, or null if there is no
+ * file open.
+ *
+ * @param caretString typically "^" which will insert "^" to indicate the
+ * caret position. If null, the caret position is not shown.
+ * @param selectBegin the text string to insert at the beginning of the selection boundary
+ * @param selectEnd the text string to insert at the end of the selection boundary
+ * @return the text contents at the current caret position
+ */
+ @Nullable
+ public String getCurrentFileContents(@Nullable final String caret, @Nullable final String selectBegin, @Nullable final String selectEnd) {
+ return execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected String executeInEDT() throws Throwable {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ CaretModel caretModel = editor.getCaretModel();
+ Caret primaryCaret = caretModel.getPrimaryCaret();
+ int offset = primaryCaret.getOffset();
+ int start = primaryCaret.getSelectionStart();
+ int end = primaryCaret.getSelectionEnd();
+ if (start == end) {
+ start = -1;
+ end = -1;
+ }
+ Document document = editor.getDocument();
+ int lineStart = 0;
+ int lineEnd = document.getTextLength();
+ String text = document.getText(new TextRange(lineStart, lineEnd));
+ StringBuilder sb = new StringBuilder(text.length() + 10);
+ for (int i = 0, n = text.length(); i < n; i++) {
+ if (selectBegin != null && start == i) {
+ sb.append(selectBegin);
+ }
+ if (caret != null && offset == i) {
+ sb.append(caret);
+ }
+ sb.append(text.charAt(i));
+ if (selectEnd != null && end == i + 1) {
+ sb.append(selectEnd);
+ }
+ }
+ return sb.toString();
+ }
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Type the given text into the editor
+ *
+ * @param text the text to type at the current editor position
+ */
+ public EditorFixture enterText(@NotNull final String text) {
+ Component component = getFocusedEditor();
+ if (component != null) {
+ robot.enterText(text);
+ }
+
+ return this;
+ }
+
+ /**
+ * Type the given text into the editor as if the user had typed it
+ * with an IME (an input method editor)
+ *
+ * @param text the text to type at the current editor position
+ */
+ public EditorFixture enterImeText(@NotNull final String text) {
+ final Component component = getFocusedEditor();
+ if (component != null && !text.isEmpty()) {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ // Simulate editing by sending the same IME events that we observe arriving from a real input method
+ int characterCount = text.length();
+ TextHitInfo caret = TextHitInfo.afterOffset(characterCount - 1);
+ TextHitInfo visiblePosition = TextHitInfo.beforeOffset(0);
+ AttributedCharacterIterator iterator = new AttributedString(text).getIterator();
+ int id = InputMethodEvent.INPUT_METHOD_TEXT_CHANGED;
+ InputMethodEvent event = new InputMethodEvent(component, id, iterator, characterCount, caret, visiblePosition);
+ component.dispatchEvent(event);
+ }
+ });
+ }
+
+ return this;
+ }
+
+ /**
+ * Press and release the given key as indicated by the {@code VK_} codes in {@link KeyEvent}.
+ * Used to transfer key presses to the editor which may have an effect but does not insert text into
+ * the editor (e.g. pressing an arrow key to move the caret)
+ *
+ * @param keyCode the key code to press
+ */
+ public EditorFixture typeKey(int keyCode) {
+ Component component = getFocusedEditor();
+ if (component != null) {
+ new ComponentDriver(robot).pressAndReleaseKeys(component, keyCode);
+ }
+ return this;
+ }
+
+ /**
+ * Press (but don't release yet) the given key as indicated by the {@code VK_} codes in {@link KeyEvent}.
+ * Used to transfer key presses to the editor which may have an effect but does not insert text into
+ * the editor (e.g. pressing an arrow key to move the caret)
+ *
+ * @param keyCode the key code to press
+ */
+ public EditorFixture pressKey(int keyCode) {
+ Component component = getFocusedEditor();
+ if (component != null) {
+ new ComponentDriver(robot).pressKey(component, keyCode);
+ }
+ return this;
+ }
+
+ /**
+ * Release the given key (as indicated by the {@code VK_} codes in {@link KeyEvent}) which
+ * must be currently pressed by a previous call to {@link #pressKey(int)}.
+ *
+ * @param keyCode the key code
+ */
+ public EditorFixture releaseKey(int keyCode) {
+ Component component = getFocusedEditor();
+ if (component != null) {
+ new ComponentDriver(robot).releaseKey(component, keyCode);
+ }
+ return this;
+ }
+
+ /**
+ * Requests focus in the editor
+ */
+ public EditorFixture requestFocus() {
+ getFocusedEditor();
+ return this;
+ }
+
+ /**
+ * Requests focus in the editor, waits and returns editor component
+ */
+ @Nullable
+ private JComponent getFocusedEditor() {
+ Editor editor = execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected Editor executeInEDT() throws Throwable {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ return manager.getSelectedTextEditor(); // Must be called from the EDT
+ }
+ });
+
+ if (editor != null) {
+ JComponent contentComponent = editor.getContentComponent();
+ new ComponentDriver(robot).focusAndWaitForFocusGain(contentComponent);
+ assertSame(contentComponent, FocusManager.getCurrentManager().getFocusOwner());
+ return contentComponent;
+ } else {
+ fail("Expected to find editor to focus, but there is no current editor");
+ return null;
+ }
+ }
+
+ /**
+ * Moves the caret to the start of the given line number (0-based).
+ *
+ * @param lineNumber the line number.
+ */
+ @NotNull
+ public EditorFixture moveToLine(final int lineNumber) {
+ assertThat(lineNumber).isGreaterThanOrEqualTo(0);
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ // TODO: Do this via mouse clicks!
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ Document document = editor.getDocument();
+ int offset = document.getLineStartOffset(lineNumber);
+ editor.getCaretModel().moveToOffset(offset);
+ editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Moves the caret to the given caret offset (0-based).
+ *
+ * @param offset the character offset.
+ */
+ public EditorFixture moveTo(final int offset) {
+ assertThat(offset).isGreaterThanOrEqualTo(0);
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ // TODO: Do this via mouse clicks!
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ editor.getCaretModel().moveToOffset(offset);
+ editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ }
+ });
+
+ return this;
+ }
+
+ /**
+ * Selects the given range. If the first and second offsets are the same, it simply
+ * moves the caret to the given position. The caret is always placed at the second offset,
+ * which is allowed to be smaller than the first offset. Calling {@code select(10, 7)}
+ * would be the same as dragging the mouse from offset 10 to offset 7 and releasing the mouse
+ * button; the caret is now at the beginning of the selection.
+ *
+ * @param firstOffset the character offset where we start the selection, or -1 to remove the selection
+ * @param secondOffset the character offset where we end the selection, which can be an earlier
+ * offset than the firstOffset
+ */
+ public EditorFixture select(final int firstOffset, final int secondOffset) {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ // TODO: Do this via mouse drags!
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ editor.getCaretModel().getPrimaryCaret().setSelection(firstOffset, secondOffset);
+ editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+ }
+ }
+ });
+
+ return this;
+ }
+
+ /**
+ * Finds the next position (or if {@code searchFromTop} is true, from the beginning) of
+ * the given string indicated by a prefix and a suffix. The offset returned will be the position exactly
+ * in the middle of the two. For example, if you have the text "The quick brown fox jumps over the lazy dog"
+ * and you search via {@code moveTo("The qui", "ck brown", true)} the returned offset will be at the 7th
+ * position in the string, between the "i" and "c".
+ *
+ * Note that on Windows, any {@code \r}'s are hidden from the editor, so they never count in offset
+ * computations.
+ *
+ * @param prefix the target prefix which must immediately precede the returned position
+ * @param suffix the target string, which must immediately follow the given prefix
+ * @param searchFromTop if true, search from the beginning of the file instead of from the current editor position
+ * @return the 0-based offset in the document, or -1 if not found.
+ */
+ public int findOffset(@Nullable final String prefix, @Nullable final String suffix, final boolean searchFromTop) {
+ assertTrue(prefix != null || suffix != null);
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected Integer executeInEDT() throws Throwable {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ Editor editor = manager.getSelectedTextEditor();
+ if (editor != null) {
+ CaretModel caretModel = editor.getCaretModel();
+ Caret primaryCaret = caretModel.getPrimaryCaret();
+ Document document = editor.getDocument();
+ String contents = document.getCharsSequence().toString();
+ String target = (prefix != null ? prefix : "") + (suffix != null ? suffix : "");
+ int targetIndex = contents.indexOf(target, searchFromTop ? 0 : primaryCaret.getOffset());
+ return targetIndex != -1 ? targetIndex + (prefix != null ? prefix.length() : 0) : -1;
+ }
+ return -1;
+ }
+ });
+ }
+
+ /**
+ * Finds the first position in the editor document indicated by the given text segment, where ^ (or if not defined, |) indicates
+ * the caret position.
+ *
+ * @param line the line segment to search for (with ^ or | indicating the caret position)
+ * @return the 0-based offset in the document, or -1 if not found.
+ */
+ public int findOffset(@NotNull final String line) {
+ int index = line.indexOf('^');
+ if (index == -1) {
+ // Also look for |. ^ has higher precedence since in many Android XML files we'll have | appearing as
+ // the XML value flag delimiter.
+ index = line.indexOf('|');
+ }
+ assertTrue("The text segment should contain a caret position indicated by ^ or |", index != -1);
+ String prefix = line.substring(0, index);
+ if (prefix.isEmpty()) {
+ prefix = null;
+ }
+ String suffix = line.substring(index + 1);
+ if (suffix.isEmpty()) {
+ suffix = null;
+ }
+ assertTrue("The text segment should have more text than just the caret position", prefix != null || suffix != null);
+ return findOffset(prefix, suffix, true);
+ }
+
+ /**
+ * Closes the current editor
+ */
+ public EditorFixture close() {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ VirtualFile currentFile = getCurrentFile();
+ if (currentFile != null) {
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ manager.closeFile(currentFile);
+ }
+ }
+ });
+ return this;
+ }
+
+ /**
+ * 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 selectEditorTab(@NotNull final Tab tab) {
+ switch (tab) {
+ case EDITOR:
+ selectEditorTab("Text");
+ break;
+ case DESIGN:
+ selectEditorTab("Design");
+ break;
+ case DEFAULT:
+ selectEditorTab((String)null);
+ break;
+ default:
+ fail("Unknown tab " + tab);
+ }
+ return this;
+ }
+
+ /**
+ * 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 selectEditorTab(@Nullable final String tabName) {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ VirtualFile currentFile = getCurrentFile();
+ assertNotNull("Can't switch to tab " + tabName + " when no file is open in the editor", currentFile);
+ FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ FileEditor[] editors = manager.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(manager).invoke(target);
+ return;
+ }
+ List tabNames = Lists.newArrayList();
+ for (FileEditor editor : editors) {
+ tabNames.add(editor.getName());
+ }
+ fail("Could not find editor tab \"" + (tabName != null ? tabName : "") + "\": 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() throws Throwable {
+ // TODO: Use UI to navigate to the file instead
+ Project project = myFrame.getProject();
+ FileEditorManager manager = FileEditorManager.getInstance(project);
+ if (tab == Tab.EDITOR) {
+ manager.openTextEditor(new OpenFileDescriptor(project, file), true);
+ }
+ else {
+ manager.openFile(file, true);
+ }
+ }
+ });
+
+ pause(new Condition("File " + quote(file.getPath()) + " to be opened") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ protected Boolean executeInEDT() throws Throwable {
+ return file.equals(getCurrentFile());
+ }
+ });
+ }
+ }, 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
+ * it. It will fail if the action is not enabled, or if it is interactive.
+ *
+ * @param action the action to invoke
+ */
+ public EditorFixture invokeAction(@NotNull EditorAction action) {
+ switch (action) {
+ case BACK_SPACE:
+ invokeActionViaKeystroke("EditorBackSpace");
+ break;
+ case UNDO:
+ invokeActionViaKeystroke("$Undo");
+ break;
+ case REDO:
+ invokeActionViaKeystroke("$Redo");
+ break;
+ case CUT:
+ invokeActionViaKeystroke("$Cut");
+ break;
+ case COPY:
+ invokeActionViaKeystroke("$Copy");
+ break;
+ case PASTE:
+ invokeActionViaKeystroke("$Paste");
+ break;
+ case SELECT_ALL:
+ invokeActionViaKeystroke("$SelectAll");
+ break;
+ case FORMAT: {
+ // To format without showing dialog:
+ // invokeActionViaKeystroke("ReformatCode");
+ // However, before we replace this, make sure the dialog isn't shown in some scenarios (e.g. first users)
+ invokeActionViaKeystroke("ShowReformatFileDialog");
+ JDialog dialog = robot.finder().find(new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ return dialog.isShowing() && dialog.getTitle().contains("Reformat");
+ }
+ });
+ DialogFixture dialogFixture = new DialogFixture(robot, dialog);
+
+ // Find and click the Run button. We can't just invoke
+ // dialogFixture.button("Run").click();
+ // because that searches by button name (which is null for the Run button), not the button *title*.
+ dialogFixture.button(new GenericTypeMatcher(JButton.class) {
+ @Override
+ protected boolean isMatching(@NotNull JButton component) {
+ return component.getText().equals("Run");
+ }
+ }).click();
+
+ break;
+ }
+ case GOTO_DECLARATION:
+ invokeActionViaKeystroke("GotoDeclaration");
+ break;
+ case COMPLETE_CURRENT_STATEMENT:
+ invokeActionViaKeystroke("EditorCompleteStatement");
+ break;
+ case SAVE:
+ invokeActionViaKeystroke("SaveAll");
+ break;
+ case TOGGLE_COMMENT:
+ invokeActionViaKeystroke("CommentByLineComment");
+ break;
+ case DUPLICATE_LINES:
+ invokeActionViaKeystroke("EditorDuplicate");
+ break;
+ case DELETE_LINE:
+ invokeActionViaKeystroke("EditorDeleteLine");
+ break;
+ case NEXT_METHOD:
+ invokeActionViaKeystroke("MethodDown");
+ break;
+ case PREVIOUS_METHOD:
+ invokeActionViaKeystroke("MethodUp");
+ break;
+ case NEXT_ERROR:
+ invokeActionViaKeystroke("GotoNextError");
+ break;
+ case PREVIOUS_ERROR:
+ invokeActionViaKeystroke("GotoPreviousError");
+ break;
+ case JOIN_LINES:
+ invokeActionViaKeystroke("EditorJoinLines");
+ break;
+ case SHOW_INTENTION_ACTIONS:
+ invokeActionViaKeystroke("ShowIntentionActions");
+ break;
+ case RUN_FROM_CONTEXT:
+ invokeActionViaKeystroke("RunClass");
+ break;
+ case EXTEND_SELECTION:
+ case SHRINK_SELECTION:
+ // Need to find the right action id's for these; didn't see them in the default keymap
+ default:
+ fail("Not yet implemented");
+ break;
+ }
+ return this;
+ }
+
+ private void invokeActionViaKeystroke(@NotNull String actionId) {
+ AnAction action = ActionManager.getInstance().getAction(actionId);
+ assertNotNull(actionId, action);
+ assertTrue(actionId + " is not enabled", action.getTemplatePresentation().isEnabled());
+
+ Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
+ Shortcut[] shortcuts = keymap.getShortcuts(actionId);
+ assertNotNull(shortcuts);
+ assertThat(shortcuts).isNotEmpty();
+ Shortcut shortcut = shortcuts[0];
+ if (shortcut instanceof KeyboardShortcut) {
+ KeyboardShortcut cs = (KeyboardShortcut)shortcut;
+ KeyStroke firstKeyStroke = cs.getFirstKeyStroke();
+ Component component = getFocusedEditor();
+ if (component != null) {
+ ComponentDriver driver = new ComponentDriver(robot);
+ System.out.println("Invoking editor action " + actionId + " via shortcut "
+ + KeyEvent.getKeyModifiersText(firstKeyStroke.getModifiers())
+ + KeyEvent.getKeyText(firstKeyStroke.getKeyCode()));
+ driver.pressAndReleaseKey(component, firstKeyStroke.getKeyCode(), new int[]{firstKeyStroke.getModifiers()});
+ KeyStroke secondKeyStroke = cs.getSecondKeyStroke();
+ if (secondKeyStroke != null) {
+ System.out.println(" and "
+ + KeyEvent.getKeyModifiersText(secondKeyStroke.getModifiers())
+ + KeyEvent.getKeyText(secondKeyStroke.getKeyCode()));
+ driver.pressAndReleaseKey(component, secondKeyStroke.getKeyCode(), new int[]{secondKeyStroke.getModifiers()});
+ }
+ } else {
+ fail("Editor not focused for action");
+ }
+ }
+ else {
+ fail("Unsupported shortcut type " + shortcut.getClass().getName());
+ }
+ }
+
+ /**
+ * 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 infos = Lists.newArrayList();
+ for (HighlightInfo info : getCurrentFileFixture().getHighlightInfos(severity)) {
+ infos.add(info.getDescription());
+ }
+ assertThat(infos).containsOnly(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;
+ }
+
+ @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
+ * one with the given label prefix
+ *
+ * @param labelPrefix the prefix of the action description to be shown
+ * @return this
+ */
+ @NotNull
+ public EditorFixture invokeIntentionAction(@NotNull String labelPrefix) {
+ invokeAction(EditorFixture.EditorAction.SHOW_INTENTION_ACTIONS);
+ JBList popup = waitForPopup(robot);
+ clickPopupMenuItem(labelPrefix, popup, robot);
+ return this;
+ }
+
+ /**
+ * Returns a fixture around the layout editor, if the currently edited file
+ * is a layout file and it is currently showing the layout editor tab or the parameter
+ * requests that it be opened if necessary
+ *
+ * @param switchToTabIfNecessary if true, switch to the design tab if it is not already showing
+ * @return a layout editor fixture, or null if the current file is not a layout file or the
+ * wrong tab is showing
+ */
+ //@Nullable
+ //public LayoutEditorFixture getLayoutEditor(boolean switchToTabIfNecessary) {
+ // VirtualFile currentFile = getCurrentFile();
+ // if (ResourceHelper.getFolderType(currentFile) != ResourceFolderType.LAYOUT) {
+ // return null;
+ // }
+ //
+ // if (switchToTabIfNecessary) {
+ // selectEditorTab(Tab.DESIGN);
+ // }
+ //
+ // return execute(new GuiQuery() {
+ // @Override
+ // @Nullable
+ // protected LayoutEditorFixture executeInEDT() throws Throwable {
+ // FileEditorManager manager = FileEditorManager.getInstance(myFrame.getProject());
+ // FileEditor[] editors = manager.getSelectedEditors();
+ // if (editors.length == 0) {
+ // return null;
+ // }
+ // FileEditor selected = editors[0];
+ // if (!(selected instanceof AndroidDesignerEditor)) {
+ // return null;
+ // }
+ //
+ // return new LayoutEditorFixture(robot, (AndroidDesignerEditor)selected);
+ // }
+ // });
+ //}
+
+ /**
+ * Returns a fixture around the layout preview window, if the currently edited file
+ * is a layout file and it the XML editor tab of the layout is currently showing.
+ *
+ * @param switchToTabIfNecessary if true, switch to the editor tab if it is not already showing
+ * @return a layout preview fixture, or null if the current file is not a layout file or the
+ * wrong tab is showing
+ */
+ //@Nullable
+ //public LayoutPreviewFixture getLayoutPreview(boolean switchToTabIfNecessary) {
+ // VirtualFile currentFile = getCurrentFile();
+ // if (ResourceHelper.getFolderType(currentFile) != ResourceFolderType.LAYOUT) {
+ // return null;
+ // }
+ //
+ // if (switchToTabIfNecessary) {
+ // selectEditorTab(Tab.EDITOR);
+ // }
+ //
+ // Boolean visible = GuiActionRunner.execute(new GuiQuery() {
+ // @Override
+ // protected Boolean executeInEDT() throws Throwable {
+ // AndroidLayoutPreviewToolWindowManager manager = AndroidLayoutPreviewToolWindowManager.getInstance(myFrame.getProject());
+ // return manager.getToolWindowForm() != null;
+ // }
+ // });
+ // if (visible == null || !visible) {
+ // myFrame.invokeMenuPath("View", "Tool Windows", "Preview");
+ // }
+ //
+ // pause(new Condition("Preview window is visible") {
+ // @Override
+ // public boolean test() {
+ // AndroidLayoutPreviewToolWindowManager manager = AndroidLayoutPreviewToolWindowManager.getInstance(myFrame.getProject());
+ // return manager.getToolWindowForm() != null;
+ // }
+ // }, SHORT_TIMEOUT);
+ //
+ // return new LayoutPreviewFixture(robot, myFrame.getProject());
+ //}
+
+
+ /**
+ * Returns a fixture around the {@link com.android.tools.idea.editors.theme.ThemeEditor} if the currently
+ * displayed editor is a theme editor.
+ */
+ //@NotNull
+ //public ThemeEditorFixture getThemeEditor() {
+ // final ThemeEditorComponent themeEditorComponent =
+ // GuiTests.waitUntilFound(robot, new GenericTypeMatcher(ThemeEditorComponent.class) {
+ // @Override
+ // protected boolean isMatching(@NotNull ThemeEditorComponent component) {
+ // return true;
+ // }
+ // });
+ //
+ // 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());
+ }
+ }
+
+
+ /**
+ * Common editor actions, invokable via {@link #invokeAction(EditorAction)}
+ */
+ public enum EditorAction {
+ SHOW_INTENTION_ACTIONS,
+ FORMAT,
+ SAVE,
+ UNDO,
+ REDO,
+ COPY,
+ PASTE,
+ CUT,
+ BACK_SPACE,
+ COMPLETE_CURRENT_STATEMENT,
+ EXTEND_SELECTION,
+ SHRINK_SELECTION,
+ SELECT_ALL,
+ JOIN_LINES,
+ DUPLICATE_LINES,
+ DELETE_LINE,
+ TOGGLE_COMMENT,
+ GOTO_DECLARATION,
+ NEXT_ERROR,
+ PREVIOUS_ERROR,
+ NEXT_METHOD,
+ PREVIOUS_METHOD,
+ RUN_FROM_CONTEXT
+ }
+
+ /**
+ * The different tabs of an editor; used by for example {@link #open(VirtualFile, EditorFixture.Tab)} to indicate which
+ * tab should be opened
+ */
+ public enum Tab { EDITOR, DESIGN, DEFAULT }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/EditorNotificationPanelFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/EditorNotificationPanelFixture.java
new file mode 100644
index 000000000000..d37ad4de55b8
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/EditorNotificationPanelFixture.java
@@ -0,0 +1,41 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.ui.EditorNotificationPanel;
+import com.intellij.ui.HyperlinkLabel;
+import org.fest.reflect.core.Reflection;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.jetbrains.annotations.NotNull;
+
+public class EditorNotificationPanelFixture extends JComponentFixture {
+ public EditorNotificationPanelFixture(@NotNull Robot robot, @NotNull EditorNotificationPanel target) {
+ super(EditorNotificationPanelFixture.class, robot, target);
+ }
+
+ public void performAction(@NotNull final String label) {
+ HyperlinkLabel link = robot().finder().find(target(), new GenericTypeMatcher(HyperlinkLabel.class) {
+ @Override
+ protected boolean isMatching(@NotNull HyperlinkLabel hyperlinkLabel) {
+ // IntelliJ's HyperLinkLabel class does not expose the getText method (it is package private)
+ return hyperlinkLabel.isShowing() &&
+ label.equals(Reflection.method("getText").withReturnType(String.class).in(hyperlinkLabel).invoke());
+ }
+ });
+ driver().click(link);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ExecutionToolWindowFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ExecutionToolWindowFixture.java
new file mode 100644
index 000000000000..b648f98e6e9f
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ExecutionToolWindowFixture.java
@@ -0,0 +1,254 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.execution.impl.ConsoleViewImpl;
+import com.intellij.execution.testframework.TestTreeView;
+import com.intellij.execution.ui.layout.impl.GridImpl;
+import com.intellij.execution.ui.layout.impl.JBRunnerTabs;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.impl.ActionButton;
+import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
+import com.intellij.openapi.ui.ThreeComponentsSplitter;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.tabs.impl.JBTabsImpl;
+import com.intellij.ui.tabs.impl.TabLabel;
+import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiActionRunner;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.exception.ComponentLookupException;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.fest.swing.timing.Timeout;
+import org.fest.swing.util.TextMatcher;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+import javax.swing.*;
+import java.util.List;
+
+import static com.intellij.tests.gui.framework.GuiTests.waitUntilFound;
+import static com.intellij.util.ui.UIUtil.findComponentOfType;
+import static com.intellij.util.ui.UIUtil.findComponentsOfType;
+import static junit.framework.Assert.assertNotNull;
+import static org.fest.reflect.core.Reflection.method;
+import static org.fest.swing.timing.Pause.pause;
+
+public class ExecutionToolWindowFixture extends ToolWindowFixture {
+ public static class ContentFixture {
+ @NotNull private final ExecutionToolWindowFixture myParentToolWindow;
+ @NotNull private final Robot myRobot;
+ @NotNull private final Content myContent;
+
+ private ContentFixture(@NotNull ExecutionToolWindowFixture parentToolWindow, @NotNull Robot robot, @NotNull Content content) {
+ myParentToolWindow = parentToolWindow;
+ myRobot = robot;
+ myContent = content;
+ }
+
+ public void waitForOutput(@NotNull final TextMatcher matcher, @NotNull Timeout timeout) {
+ pause(new Condition("LogCat tool window output check for package name.") {
+ @Override
+ public boolean test() {
+ return outputMatches(matcher);
+ }
+ }, timeout);
+ }
+
+ /**
+ * Waits until it grabs the console window and then returns true if its text matches that of {@code matcher}.
+ * Note: The caller should wrap it in something like a org.fest.swing.timing.Pause.pause to make sure they don't hang forever if the
+ * console view cannot be found for some reason.
+ */
+ public boolean outputMatches(@NotNull TextMatcher matcher) {
+ ConsoleViewImpl consoleView;
+ while ((consoleView = findConsoleView()) == null || consoleView.getEditor() == null) {
+ // If our handle has been replaced, find it again.
+ JComponent consoleComponent = getTabComponent("Console");
+ myRobot.click(consoleComponent);
+ }
+ return matcher.isMatching(consoleView.getEditor().getDocument().getText());
+ }
+
+ // Returns the console or null if it is not found.
+ @Nullable
+ private ConsoleViewImpl findConsoleView() {
+ try {
+ return myRobot.finder().findByType(myParentToolWindow.myToolWindow.getComponent(), ConsoleViewImpl.class, false);
+ } catch (ComponentLookupException e) {
+ return null;
+ }
+ }
+
+ @NotNull
+ public JComponent getTabComponent(@NotNull final String tabName) {
+ return getTabContent(myParentToolWindow.myToolWindow.getComponent(), JBRunnerTabs.class, GridImpl.class, tabName);
+ }
+
+ @NotNull
+ public UnitTestTreeFixture getUnitTestTree() {
+ return new UnitTestTreeFixture(this, myRobot.finder().findByType(myContent.getComponent(), TestTreeView.class));
+ }
+
+ // Returns the root of the debugger tree or null.
+ @Nullable
+ public XDebuggerTreeNode getDebuggerTreeRoot() {
+ try {
+ JComponent debuggerComponent = getTabComponent("Debugger");
+ if (debuggerComponent != null) {
+ myRobot.click(debuggerComponent);
+ }
+ ThreeComponentsSplitter threeComponentsSplitter =
+ myRobot.finder().findByType(debuggerComponent, ThreeComponentsSplitter.class, false);
+ JComponent innerComponent = threeComponentsSplitter.getInnerComponent();
+ assertNotNull(innerComponent);
+ return myRobot.finder().findByType(innerComponent, XDebuggerTree.class, false).getRoot();
+ } catch (ComponentLookupException e) {
+ return null;
+ }
+ }
+
+ public void clickDebuggerTreeRoot() {
+ try {
+ JComponent debuggerComponent = getTabComponent("Debugger");
+ myRobot.click(debuggerComponent);
+ } catch (ComponentLookupException e) { }
+ }
+
+ @NotNull
+ private JComponent getTabContent(@NotNull final JComponent root,
+ final Class extends JBTabsImpl> parentComponentType,
+ @NotNull final Class extends JComponent> tabContentType,
+ @NotNull final String tabName) {
+ myParentToolWindow.activate();
+ myParentToolWindow.waitUntilIsVisible();
+
+ TabLabel tabLabel;
+ if (parentComponentType == null) {
+ tabLabel = waitUntilFound(myRobot, new GenericTypeMatcher(TabLabel.class) {
+ @Override
+ protected boolean isMatching(@NotNull TabLabel component) {
+ return component.toString().equals(tabName);
+ }
+ });
+ }
+ else {
+ final JComponent parent = myRobot.finder().findByType(root, parentComponentType, false);
+ tabLabel = waitUntilFound(myRobot, parent, new GenericTypeMatcher(TabLabel.class) {
+ @Override
+ protected boolean isMatching(@NotNull TabLabel component) {
+ return component.getParent() == parent && component.toString().equals(tabName);
+ }
+ });
+ }
+ myRobot.click(tabLabel);
+ return myRobot.finder().findByType(tabContentType);
+ }
+
+ public boolean isExecutionInProgress() {
+ // Consider that execution is in progress if 'stop' toolbar button is enabled.
+ for (ActionButton button : getToolbarButtons()) {
+ if ("com.intellij.execution.actions.StopAction".equals(button.getAction().getClass().getCanonicalName())) {
+ //noinspection ConstantConditions
+ return method("isButtonEnabled").withReturnType(boolean.class).in(button).invoke();
+ }
+ }
+ return true;
+ }
+
+ public void rerun() {
+ for (ActionButton button : getToolbarButtons()) {
+ if ("com.intellij.execution.runners.FakeRerunAction".equals(button.getAction().getClass().getCanonicalName())) {
+ myRobot.click(button);
+ return;
+ }
+ }
+
+ throw new IllegalStateException("Could not find the Re-run button.");
+ }
+
+ public void rerunFailed() {
+ for (ActionButton button : getToolbarButtons()) {
+ if ("com.intellij.execution.junit2.ui.actions.RerunFailedTestsAction".equals(button.getAction().getClass().getCanonicalName())) {
+ myRobot.click(button);
+ return;
+ }
+ }
+
+ throw new IllegalStateException("Could not find the Re-run failed tests button.");
+ }
+
+ public void waitForExecutionToFinish(@NotNull Timeout timeout) {
+ Pause.pause(new Condition("Wait for execution to finish") {
+ @Override
+ public boolean test() {
+ return !isExecutionInProgress();
+ }
+ }, timeout);
+ }
+
+ @TestOnly
+ public boolean stop() {
+ for (final ActionButton button : getToolbarButtons()) {
+ final AnAction action = button.getAction();
+ if (action != null && action.getClass().getName().equals("com.intellij.execution.actions.StopAction")) {
+ //noinspection ConstantConditions
+ boolean enabled = method("isButtonEnabled").withReturnType(boolean.class).in(button).invoke();
+ if (enabled) {
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ button.click();
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ private List getToolbarButtons() {
+ ActionToolbarImpl toolbar = findComponentOfType(myContent.getComponent(), ActionToolbarImpl.class);
+ assert toolbar != null;
+ return findComponentsOfType(toolbar, ActionButton.class);
+ }
+ } // End class ContentFixture
+
+ protected ExecutionToolWindowFixture(@NotNull String toolWindowId, @NotNull IdeFrameFixture ideFrame) {
+ super(toolWindowId, ideFrame.getProject(), ideFrame.robot());
+ }
+
+ @NotNull
+ public ContentFixture findContent(@NotNull String tabName) {
+ Content content = getContent(tabName);
+ assertNotNull(content);
+ return new ContentFixture(this, myRobot, content);
+ }
+
+ @NotNull
+ public ContentFixture findContent(@NotNull TextMatcher tabNameMatcher) {
+ Content content = getContent(tabNameMatcher);
+ assertNotNull(content);
+ return new ContentFixture(this, myRobot, content);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/FileChooserDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/FileChooserDialogFixture.java
new file mode 100644
index 000000000000..7065037dc9e7
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/FileChooserDialogFixture.java
@@ -0,0 +1,113 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.fileChooser.ex.FileChooserDialogImpl;
+import com.intellij.openapi.fileChooser.ex.FileSystemTreeImpl;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static com.intellij.tests.gui.framework.GuiTests.findAndClickOkButton;
+import static org.fest.reflect.core.Reflection.field;
+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.assertNotNull;
+
+public class FileChooserDialogFixture extends IdeaDialogFixture {
+ @NotNull
+ public static FileChooserDialogFixture findOpenProjectDialog(@NotNull Robot robot) {
+ return findDialog(robot, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ return dialog.isShowing() && "Open File or Project".equals(dialog.getTitle());
+ }
+ });
+ }
+
+ @NotNull
+ public static FileChooserDialogFixture findImportProjectDialog(@NotNull Robot robot) {
+ return findDialog(robot, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ String title = dialog.getTitle();
+ return dialog.isShowing() && title != null && title.startsWith("Select") && title.endsWith("Project to Import");
+ }
+ });
+ }
+
+ @NotNull
+ public static FileChooserDialogFixture findDialog(@NotNull Robot robot, @NotNull final GenericTypeMatcher matcher) {
+ return new FileChooserDialogFixture(robot, find(robot, FileChooserDialogImpl.class, matcher));
+ }
+
+ private FileChooserDialogFixture(@NotNull Robot robot, @NotNull DialogAndWrapper dialogAndWrapper) {
+ super(robot, dialogAndWrapper);
+ }
+
+ @NotNull
+ public FileChooserDialogFixture select(@NotNull final VirtualFile file) {
+ sleepWithTimeBomb();
+ final FileSystemTreeImpl fileSystemTree = field("myFileSystemTree").ofType(FileSystemTreeImpl.class)
+ .in(getDialogWrapper())
+ .get();
+ assertNotNull(fileSystemTree);
+ final AtomicBoolean fileSelected = new AtomicBoolean();
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ fileSystemTree.select(file, new Runnable() {
+ @Override
+ public void run() {
+ fileSelected.set(true);
+ }
+ });
+ }
+ });
+
+ pause(new Condition("File " + quote(file.getPath()) + " is selected") {
+ @Override
+ public boolean test() {
+ return fileSelected.get();
+ }
+ }, SHORT_TIMEOUT);
+
+ return this;
+ }
+
+ private void sleepWithTimeBomb() {
+ //TODO: WTF?!!
+ assert System.currentTimeMillis() < 1452600000000L; // 2016-01-12 12:00
+ try {
+ Thread.sleep(5000);
+ }
+ catch (InterruptedException e) {}
+ }
+
+ @NotNull
+ public FileChooserDialogFixture clickOk() {
+ findAndClickOkButton(this);
+ return this;
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/FileFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/FileFixture.java
new file mode 100644
index 000000000000..73b1d278ac78
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/FileFixture.java
@@ -0,0 +1,196 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
+import com.intellij.codeInsight.daemon.impl.HighlightInfo;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.CommonProcessors;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.io.File;
+import java.util.Collection;
+
+import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile;
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static junit.framework.Assert.assertNotNull;
+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;
+
+public class FileFixture {
+ @NotNull private final Project myProject;
+ @NotNull private final File myPath;
+ @NotNull private final VirtualFile myVirtualFile;
+
+ public FileFixture(@NotNull Project project, @NotNull VirtualFile file) {
+ myProject = project;
+ myPath = virtualToIoFile(file);
+ myVirtualFile = file;
+ }
+
+ @NotNull
+ public FileFixture requireOpenAndSelected() {
+ requireVirtualFile();
+ pause(new Condition("File " + quote(myPath.getPath()) + " to be opened") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ protected Boolean executeInEDT() throws Throwable {
+ return isOpenAndSelected();
+ }
+ });
+ }
+ }, SHORT_TIMEOUT);
+ return this;
+ }
+
+ private boolean isOpenAndSelected() {
+ FileEditorManager editorManager = FileEditorManager.getInstance(myProject);
+ FileEditor selectedEditor = editorManager.getSelectedEditor(myVirtualFile);
+ if (selectedEditor != null) {
+ JComponent component = selectedEditor.getComponent();
+ if (component.isVisible() && component.isShowing()) {
+ Document document = FileDocumentManager.getInstance().getDocument(myVirtualFile);
+ if (document != null) {
+ PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
+ if (psiFile != null) {
+ DaemonCodeAnalyzerEx codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject);
+ //noinspection ConstantConditions
+ boolean isRunning = method("isRunning").withReturnType(boolean.class).in(codeAnalyzer).invoke();
+ return !isRunning;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ public FileFixture waitUntilErrorAnalysisFinishes() {
+ pause(new Condition("error analysis finishes") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ protected Boolean executeInEDT() throws Throwable {
+ return DaemonCodeAnalyzerEx.getInstanceEx(myProject).isErrorAnalyzingFinished(getPsiFile());
+ }
+ });
+ }
+ }, SHORT_TIMEOUT);
+ return this;
+ }
+
+ @NotNull
+ public FileFixture requireCodeAnalysisHighlightCount(@NotNull HighlightSeverity severity, int expected) {
+ Collection highlightInfos = getHighlightInfos(severity);
+ assertThat(highlightInfos).hasSize(expected);
+ return this;
+ }
+
+ @NotNull
+ public Collection getHighlightInfos(@NotNull final HighlightSeverity severity) {
+ waitUntilErrorAnalysisFinishes();
+
+ final Document document = getNotNullDocument();
+ Collection highlightInfos = execute(new GuiQuery>() {
+ @Override
+ protected Collection executeInEDT() throws Throwable {
+ CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor();
+ DaemonCodeAnalyzerEx.processHighlights(document, myProject, severity, 0, document.getTextLength(), processor);
+ return processor.getResults();
+ }
+ });
+ assert highlightInfos != null;
+ return highlightInfos;
+ }
+
+ @NotNull
+ private PsiFile getPsiFile() {
+ final PsiFile psiFile = execute(new GuiQuery() {
+ @Override
+ protected PsiFile executeInEDT() throws Throwable {
+ return PsiManager.getInstance(myProject).findFile(myVirtualFile);
+ }
+ });
+ assertNotNull("No Psi file found for path " + quote(myVirtualFile.getPath()), psiFile);
+ return psiFile;
+ }
+
+ @NotNull
+ public FileFixture waitForCodeAnalysisHighlightCount(@NotNull final HighlightSeverity severity, final int expected) {
+ final Document document = getNotNullDocument();
+ pause(new Condition("Waiting for code analysis " + severity + " count to reach " + expected) {
+ @Override
+ public boolean test() {
+ Collection highlightInfos = execute(new GuiQuery>() {
+ @Override
+ protected Collection executeInEDT() throws Throwable {
+ CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor();
+ DaemonCodeAnalyzerEx.processHighlights(document, myProject, severity, 0, document.getTextLength(), processor);
+ return processor.getResults();
+ }
+ });
+ assertNotNull(highlightInfos);
+ return highlightInfos.size() == expected;
+ }
+ }, SHORT_TIMEOUT);
+
+ return this;
+ }
+
+ @NotNull
+ private Document getNotNullDocument() {
+ Document document = getDocument(myVirtualFile);
+ assertNotNull("No Document found for path " + quote(myPath.getPath()), document);
+ return document;
+ }
+
+ @NotNull
+ public FileFixture requireVirtualFile() {
+ assertNotNull("No VirtualFile found for path " + quote(myPath.getPath()), myVirtualFile);
+ return this;
+ }
+
+ @Nullable
+ public static Document getDocument(@NotNull final VirtualFile file) {
+ return execute(new GuiQuery() {
+ @Override
+ protected Document executeInEDT() throws Throwable {
+ return FileDocumentManager.getInstance().getDocument(file);
+ }
+ });
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/FindDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/FindDialogFixture.java
new file mode 100644
index 000000000000..93393d128fce
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/FindDialogFixture.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.intellij.find.impl.FindDialog;
+import com.intellij.openapi.ui.ComboBox;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiTask;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+import static com.intellij.tests.gui.framework.GuiTests.findAndClickButton;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+import static org.junit.Assert.assertNotNull;
+
+public class FindDialogFixture extends IdeaDialogFixture {
+ @NotNull
+ public static FindDialogFixture find(@NotNull Robot robot) {
+ return new FindDialogFixture(robot, find(robot, FindDialog.class));
+ }
+
+ private FindDialogFixture(@NotNull Robot robot, @NotNull DialogAndWrapper dialogAndWrapper) {
+ super(robot, dialogAndWrapper);
+ }
+
+ @NotNull
+ public FindDialogFixture setTextToFind(@NotNull final String text) {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ JComponent c = getDialogWrapper().getPreferredFocusedComponent();
+ assertThat(c).isInstanceOf(ComboBox.class);
+ ComboBox input = (ComboBox)c;
+ assertNotNull(input);
+ input.setSelectedItem(text);
+ }
+ });
+ return this;
+ }
+
+ @NotNull
+ public FindDialogFixture clickFind() {
+ findAndClickButton(this, "Find");
+ return this;
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/FindToolWindowFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/FindToolWindowFixture.java
new file mode 100644
index 000000000000..01c8dc519f61
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/FindToolWindowFixture.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.intellij.ide.OccurenceNavigatorSupport;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.treeStructure.Tree;
+import com.intellij.usageView.UsageViewManager;
+import com.intellij.usages.impl.GroupNode;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.util.Lists;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.List;
+
+import static org.fest.reflect.core.Reflection.field;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+import static org.junit.Assert.assertNotNull;
+
+public class FindToolWindowFixture {
+ @NotNull private final IdeFrameFixture myParent;
+
+ public FindToolWindowFixture(@NotNull IdeFrameFixture parent) {
+ myParent = parent;
+ }
+
+ @NotNull
+ public ContentFixture getSelectedContext() {
+ return new ContentFixture(myParent);
+ }
+
+ public static class ContentFixture {
+ @NotNull private final Content myContent;
+
+ ContentFixture(@NotNull IdeFrameFixture parent) {
+ UsageViewManager usageViewManager = UsageViewManager.getInstance(parent.getProject());
+ myContent = usageViewManager.getSelectedContent();
+ assertNotNull(myContent);
+ }
+
+
+ public void findUsagesInGeneratedCodeGroup() {
+ findUsageGroup("Usages in generated code");
+ }
+
+ public void findUsageGroup(@NotNull final String groupText) {
+ final Tree tree = getContentsTree();
+ final List groupNames = Lists.newArrayList();
+ GroupNode foundGroup = execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected GroupNode executeInEDT() throws Throwable {
+ GroupNode rootNode = (GroupNode)tree.getModel().getRoot();
+ GroupNode found = null;
+ for (GroupNode subGroup : rootNode.getSubGroups()) {
+ String subGroupText = subGroup.getGroup().getText(null);
+ groupNames.add(subGroupText);
+ if (groupText.equals(subGroupText)) {
+ found = subGroup;
+ }
+ }
+ return found;
+ }
+ });
+ String msg = String.format("Failed to find usage group '%1$s' in %2$s", groupText, groupNames);
+ assertNotNull(msg, foundGroup);
+ }
+
+ @NotNull
+ private Tree getContentsTree() {
+ JComponent component = myContent.getComponent();
+ OccurenceNavigatorSupport navigatorSupport = field("mySupport").ofType(OccurenceNavigatorSupport.class).in(component).get();
+ assertNotNull(navigatorSupport);
+ JTree tree = field("myTree").ofType(JTree.class).in(navigatorSupport).get();
+ assertNotNull(tree);
+ return (Tree)tree;
+ }
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/IdeFrameFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/IdeFrameFixture.java
new file mode 100644
index 000000000000..26d6410a59bd
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/IdeFrameFixture.java
@@ -0,0 +1,868 @@
+/*
+ * Copyright 2000-2016 JetBrains s.r.o.
+ *
+ * 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.intellij.tests.gui.fixtures;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.intellij.codeInspection.ui.InspectionTree;
+import com.intellij.ide.RecentProjectsManager;
+import com.intellij.ide.actions.ShowSettingsUtilImpl;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.externalSystem.model.ExternalSystemException;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.options.ShowSettingsUtil;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.SourceFolder;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.impl.IdeFrameImpl;
+import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
+import com.intellij.ui.EditorNotificationPanel;
+import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.core.matcher.JButtonMatcher;
+import org.fest.swing.core.matcher.JLabelMatcher;
+import org.fest.swing.edt.GuiQuery;
+import org.fest.swing.edt.GuiTask;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Timeout;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
+
+import javax.swing.*;
+import javax.swing.tree.TreeNode;
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Set;
+
+import static com.intellij.ide.impl.ProjectUtil.closeAndDispose;
+import static com.intellij.openapi.util.io.FileUtil.*;
+import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
+import static com.intellij.openapi.vfs.VfsUtilCore.urlToPath;
+import static com.intellij.tests.gui.framework.GuiTests.*;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.fail;
+import static org.fest.assertions.Assertions.assertThat;
+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.*;
+
+/**
+ * Created by karashevich on 30/05/16.
+ */
+public class IdeFrameFixture extends ComponentFixture {
+ @NotNull private final File myProjectPath;
+
+ private EditorFixture myEditor;
+
+ @NotNull
+ public static IdeFrameFixture find(@NotNull final Robot robot, @NotNull final File projectPath, @Nullable final String projectName) {
+ final GenericTypeMatcher matcher = new GenericTypeMatcher(IdeFrameImpl.class) {
+ @Override
+ protected boolean isMatching(@NotNull IdeFrameImpl frame) {
+ Project project = frame.getProject();
+ if (project != null && toSystemIndependentName(projectPath.getPath()).equals(project.getBasePath())) {
+ return projectName == null || projectName.equals(project.getName());
+ }
+ return false;
+ }
+ };
+
+ pause(new Condition("IdeFrame " + quote(projectPath.getPath()) + " to show up") {
+ @Override
+ public boolean test() {
+ Collection frames = robot.finder().findAll(matcher);
+ return !frames.isEmpty();
+ }
+ }, LONG_TIMEOUT);
+
+ IdeFrameImpl ideFrame = robot.finder().find(matcher);
+ return new IdeFrameFixture(robot, ideFrame, projectPath);
+ }
+
+ public IdeFrameFixture(@NotNull Robot robot, @NotNull IdeFrameImpl target, @NotNull File projectPath) {
+ super(IdeFrameFixture.class, robot, target);
+ myProjectPath = projectPath;
+ final Project project = getProject();
+
+ Disposable disposable = new NoOpDisposable();
+ Disposer.register(project, disposable);
+
+ //myGradleProjectEventListener = new GradleProjectEventListener();
+
+ //GradleSyncState.subscribe(project, myGradleProjectEventListener);
+ //PostProjectBuildTasksExecutor.subscribe(project, myGradleProjectEventListener);
+ }
+
+ @NotNull
+ public File getProjectPath() {
+ return myProjectPath;
+ }
+
+ @NotNull
+ public List getModuleNames() {
+ List names = Lists.newArrayList();
+ for (Module module : getModuleManager().getModules()) {
+ names.add(module.getName());
+ }
+ return names;
+ }
+
+ @NotNull
+ public IdeFrameFixture requireModuleCount(int expected) {
+ Module[] modules = getModuleManager().getModules();
+ assertThat(modules).as("Module count in project " + quote(getProject().getName())).hasSize(expected);
+ return this;
+ }
+
+
+ @NotNull
+ public Collection getSourceFolderRelativePaths(@NotNull String moduleName, @NotNull final JpsModuleSourceRootType> sourceType) {
+ final Set paths = Sets.newHashSet();
+
+ Module module = getModule(moduleName);
+ final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
+
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ ModifiableRootModel rootModel = moduleRootManager.getModifiableModel();
+ try {
+ for (ContentEntry contentEntry : rootModel.getContentEntries()) {
+ for (SourceFolder folder : contentEntry.getSourceFolders()) {
+ JpsModuleSourceRootType> rootType = folder.getRootType();
+ if (rootType.equals(sourceType)) {
+ String path = urlToPath(folder.getUrl());
+ String relativePath = getRelativePath(myProjectPath, new File(toSystemDependentName(path)));
+ paths.add(relativePath);
+ }
+ }
+ }
+ }
+ finally {
+ rootModel.dispose();
+ }
+ }
+ });
+
+ return paths;
+ }
+
+ @NotNull
+ public Module getModule(@NotNull String name) {
+ Module module = findModule(name);
+ assertNotNull("Unable to find module with name " + quote(name), module);
+ return module;
+ }
+
+ @Nullable
+ public Module findModule(@NotNull String name) {
+ for (Module module : getModuleManager().getModules()) {
+ if (name.equals(module.getName())) {
+ return module;
+ }
+ }
+ return null;
+ }
+
+
+ @NotNull
+ private ModuleManager getModuleManager() {
+ return ModuleManager.getInstance(getProject());
+ }
+
+ @NotNull
+ public EditorFixture getEditor() {
+ if (myEditor == null) {
+ myEditor = new EditorFixture(robot(), this);
+ }
+
+ return myEditor;
+ }
+
+
+ //@NotNull
+ //public GradleInvocationResult invokeProjectMake() {
+ // return invokeProjectMake(null);
+ //}
+
+ //@NotNull
+ //public GradleInvocationResult invokeProjectMake(@Nullable Runnable executeAfterInvokingMake) {
+ // myGradleProjectEventListener.reset();
+ //
+ // final AtomicReference resultRef = new AtomicReference();
+ // AndroidProjectBuildNotifications.subscribe(getProject(), new AndroidProjectBuildNotifications.AndroidProjectBuildListener() {
+ // @Override
+ // public void buildComplete(@NotNull AndroidProjectBuildNotifications.BuildContext context) {
+ // if (context instanceof GradleBuildContext) {
+ // resultRef.set(((GradleBuildContext)context).getBuildResult());
+ // }
+ // }
+ // });
+ // selectProjectMakeAction();
+ //
+ // if (executeAfterInvokingMake != null) {
+ // executeAfterInvokingMake.run();
+ // }
+ //
+ // waitForBuildToFinish(COMPILE_JAVA);
+ //
+ // GradleInvocationResult result = resultRef.get();
+ // assertNotNull(result);
+ //
+ // return result;
+ //}
+
+ @NotNull
+ public IdeFrameFixture invokeProjectMakeAndSimulateFailure(@NotNull final String failure) {
+ Runnable failTask = new Runnable() {
+ @Override
+ public void run() {
+ throw new ExternalSystemException(failure);
+ }
+ };
+ //ApplicationManager.getApplication().putUserData(EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY, failTask);
+ selectProjectMakeAction();
+ return this;
+ }
+
+
+
+ /**
+ * Finds the Run button in the IDE interface.
+ *
+ * @return ActionButtonFixture for the run button.
+ */
+ @NotNull
+ public ActionButtonFixture findRunApplicationButton() {
+ return findActionButtonByActionId("Run");
+ }
+
+ public void debugApp(@NotNull String appName) throws ClassNotFoundException {
+ selectApp(appName);
+ findActionButtonByActionId("Debug").click();
+ }
+
+ public void runApp(@NotNull String appName) throws ClassNotFoundException {
+ selectApp(appName);
+ findActionButtonByActionId("Run").click();
+ }
+
+
+ @NotNull
+ public RunToolWindowFixture getRunToolWindow() {
+ return new RunToolWindowFixture(this);
+ }
+
+ @NotNull
+ public DebugToolWindowFixture getDebugToolWindow() {
+ return new DebugToolWindowFixture(this);
+ }
+
+ protected void selectProjectMakeAction() {
+ invokeMenuPath("Build", "Make Project");
+ }
+
+ /**
+ * Invokes an action by menu path
+ *
+ * @param path the series of menu names, e.g. {@link invokeActionByMenuPath("Build", "Make Project")}
+ */
+ public void invokeMenuPath(@NotNull String... path) {
+ getMenuFixture().invokeMenuPath(path);
+ }
+
+ /**
+ * Invokes an action by menu path (where each segment is a regular expression). This is particularly
+ * useful when the menu items can change dynamically, such as the labels of Undo actions, Run actions,
+ * etc.
+ *
+ * @param path the series of menu name regular expressions, e.g. {@link invokeActionByMenuPath("Build", "Make( Project)?")}
+ */
+ public void invokeMenuPathRegex(@NotNull String... path) {
+ getMenuFixture().invokeMenuPathRegex(path);
+ }
+
+ @NotNull
+ private MenuFixture getMenuFixture() {
+ return new MenuFixture(robot(), target());
+ }
+
+ //@NotNull
+ //public IdeFrameFixture waitForBuildToFinish(@NotNull final BuildMode buildMode) {
+ // final Project project = getProject();
+ // if (buildMode == SOURCE_GEN && !GradleProjectBuilder.getInstance(project).isSourceGenerationEnabled()) {
+ // return this;
+ // }
+ //
+ // pause(new Condition("Build (" + buildMode + ") for project " + quote(project.getName()) + " to finish'") {
+ // @Override
+ // public boolean test() {
+ // if (buildMode == SOURCE_GEN) {
+ // PostProjectBuildTasksExecutor tasksExecutor = PostProjectBuildTasksExecutor.getInstance(project);
+ // if (tasksExecutor.getLastBuildTimestamp() > -1) {
+ // // This will happen when creating a new project. Source generation happens before the IDE frame is found and build listeners
+ // // are created. It is fairly safe to assume that source generation happened if we have a timestamp for a "last performed build".
+ // return true;
+ // }
+ // }
+ // return myGradleProjectEventListener.isBuildFinished(buildMode);
+ // }
+ // }, LONG_TIMEOUT);
+ //
+ // waitForBackgroundTasksToFinish();
+ // robot().waitForIdle();
+ //
+ // return this;
+ //}
+
+ @NotNull
+ public FileFixture findExistingFileByRelativePath(@NotNull String relativePath) {
+ VirtualFile file = findFileByRelativePath(relativePath, true);
+ return new FileFixture(getProject(), file);
+ }
+
+ @Nullable
+ @Contract("_, true -> !null")
+ public VirtualFile findFileByRelativePath(@NotNull String relativePath, boolean requireExists) {
+ //noinspection Contract
+ assertFalse("Should use '/' in test relative paths, not File.separator", relativePath.contains("\\"));
+ Project project = getProject();
+ VirtualFile file = project.getBaseDir().findFileByRelativePath(relativePath);
+ if (requireExists) {
+ //noinspection Contract
+ assertNotNull("Unable to find file with relative path " + quote(relativePath), file);
+ }
+ return file;
+ }
+
+ //@NotNull
+ //public IdeFrameFixture requestProjectSyncAndExpectFailure() {
+ // requestProjectSync();
+ // return waitForGradleProjectSyncToFail();
+ //}
+
+ @NotNull
+ public IdeFrameFixture requestProjectSyncAndSimulateFailure(@NotNull final String failure) {
+ Runnable failTask = new Runnable() {
+ @Override
+ public void run() {
+ throw new ExternalSystemException(failure);
+ }
+ };
+ //ApplicationManager.getApplication().putUserData(EXECUTE_BEFORE_PROJECT_SYNC_TASK_IN_GUI_TEST_KEY, failTask);
+ // When simulating the error, we don't have to wait for sync to happen. Sync never happens because the error is thrown before it (sync)
+ // is started.
+ return requestProjectSync();
+ }
+
+ @NotNull
+ public IdeFrameFixture requestProjectSync() {
+ //myGradleProjectEventListener.reset();
+
+ // We wait until all "Run Configurations" are populated in the toolbar combo-box. Until then the "Project Sync" button is not in its
+ // final position, and FEST will click the wrong button.
+ pause(new Condition("Waiting for 'Run Configurations' to be populated") {
+ @Override
+ public boolean test() {
+ RunConfigurationComboBoxFixture runConfigurationComboBox = RunConfigurationComboBoxFixture.find(IdeFrameFixture.this);
+ return isNotEmpty(runConfigurationComboBox.getText());
+ }
+ }, SHORT_TIMEOUT);
+
+ waitForBackgroundTasksToFinish();
+ findGradleSyncAction().waitUntilEnabledAndShowing();
+ // TODO figure out why in IDEA 15 even though an action is enabled, visible and showing, clicking it (via UI testing infrastructure)
+ // does not work consistently
+
+ return this;
+ }
+
+ @NotNull
+ private ActionButtonFixture findGradleSyncAction() {
+ return findActionButtonByActionId("Android.SyncProject");
+ }
+
+ //@NotNull
+ //public IdeFrameFixture waitForGradleProjectSyncToFail() {
+ // try {
+ // waitForGradleProjectSyncToFinish(true);
+ // fail("Expecting project sync to fail");
+ // }
+ // catch (RuntimeException expected) {
+ // // expected failure.
+ // }
+ // return waitForBackgroundTasksToFinish();
+ //}
+
+ //@NotNull
+ //public IdeFrameFixture waitForGradleProjectSyncToStart() {
+ // Project project = getProject();
+ // final GradleSyncState syncState = GradleSyncState.getInstance(project);
+ // if (!syncState.isSyncInProgress()) {
+ // pause(new Condition("Syncing project " + quote(project.getName()) + " to finish") {
+ // @Override
+ // public boolean test() {
+ // return myGradleProjectEventListener.isSyncStarted();
+ // }
+ // }, SHORT_TIMEOUT);
+ // }
+ // return this;
+ //}
+
+ //@NotNull
+ //public IdeFrameFixture waitForGradleProjectSyncToFinish() {
+ // waitForGradleProjectSyncToFinish(false);
+ // return this;
+ //}
+
+ //private void waitForGradleProjectSyncToFinish(final boolean expectSyncFailure) {
+ // final Project project = getProject();
+ //
+ // // ensure GradleInvoker (in-process build) is always enabled.
+ // AndroidGradleBuildConfiguration buildConfiguration = AndroidGradleBuildConfiguration.getInstance(project);
+ // buildConfiguration.USE_EXPERIMENTAL_FASTER_BUILD = true;
+ //
+ // pause(new Condition("Syncing project " + quote(project.getName()) + " to finish") {
+ // @Override
+ // public boolean test() {
+ // GradleSyncState syncState = GradleSyncState.getInstance(project);
+ // boolean syncFinished =
+ // (myGradleProjectEventListener.isSyncFinished() || syncState.isSyncNeeded() != ThreeState.YES) && !syncState.isSyncInProgress();
+ // if (expectSyncFailure) {
+ // syncFinished = syncFinished && myGradleProjectEventListener.hasSyncError();
+ // }
+ // return syncFinished;
+ // }
+ // }, LONG_TIMEOUT);
+ //
+ // findGradleSyncAction().waitUntilEnabledAndShowing();
+ //
+ // if (myGradleProjectEventListener.hasSyncError()) {
+ // RuntimeException syncError = myGradleProjectEventListener.getSyncError();
+ // myGradleProjectEventListener.reset();
+ // throw syncError;
+ // }
+ //
+ // if (!myGradleProjectEventListener.isSyncSkipped()) {
+ // waitForBuildToFinish(SOURCE_GEN);
+ // }
+ //
+ // waitForBackgroundTasksToFinish();
+ //}
+
+ @NotNull
+ public IdeFrameFixture waitForBackgroundTasksToFinish() {
+ pause(new Condition("Background tasks to finish") {
+ @Override
+ public boolean test() {
+ ProgressManager progressManager = ProgressManager.getInstance();
+ return !progressManager.hasModalProgressIndicator() &&
+ !progressManager.hasProgressIndicator() &&
+ !progressManager.hasUnsafeProgressIndicator();
+ }
+ }, LONG_TIMEOUT);
+ robot().waitForIdle();
+ return this;
+ }
+
+ @NotNull
+ private ActionButtonFixture findActionButtonByActionId(String actionId) {
+ return ActionButtonFixture.findByActionId(actionId, robot(), target());
+ }
+
+ @NotNull
+ public MessagesToolWindowFixture getMessagesToolWindow() {
+ return new MessagesToolWindowFixture(getProject(), robot());
+ }
+
+
+ @NotNull
+ public EditorNotificationPanelFixture requireEditorNotification(@NotNull final String message) {
+ final Ref notificationPanelRef = new Ref();
+
+ pause(new Condition("Notification with message '" + message + "' shows up") {
+ @Override
+ public boolean test() {
+ EditorNotificationPanel notificationPanel = findNotificationPanel(message);
+ notificationPanelRef.set(notificationPanel);
+ return notificationPanel != null;
+ }
+ });
+
+ EditorNotificationPanel notificationPanel = notificationPanelRef.get();
+ assertNotNull(notificationPanel);
+ return new EditorNotificationPanelFixture(robot(), notificationPanel);
+ }
+
+ public void requireNoEditorNotification() {
+ assertNull(findNotificationPanel(null));
+ }
+
+ /**
+ * Locates an editor notification with the given main message (unless the message is {@code null}, in which case we assert that there are
+ * no visible editor notifications. Will fail if the given notification is not found.
+ */
+ @Nullable
+ private EditorNotificationPanel findNotificationPanel(@Nullable String message) {
+ Collection panels = robot().finder().findAll(target(), new GenericTypeMatcher(
+ EditorNotificationPanel.class, true) {
+ @Override
+ protected boolean isMatching(@NotNull EditorNotificationPanel panel) {
+ return panel.isShowing();
+ }
+ });
+
+ if (message == null) {
+ if (!panels.isEmpty()) {
+ List labels = Lists.newArrayList();
+ for (EditorNotificationPanel panel : panels) {
+ labels.addAll(getEditorNotificationLabels(panel));
+ }
+ fail("Found editor notifications when none were expected" + labels);
+ }
+ return null;
+ }
+
+ List labels = Lists.newArrayList();
+ for (EditorNotificationPanel panel : panels) {
+ List found = getEditorNotificationLabels(panel);
+ labels.addAll(found);
+ for (String label : found) {
+ if (label.contains(message)) {
+ return panel;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Looks up the main label for a given editor notification panel
+ */
+ private List getEditorNotificationLabels(@NotNull EditorNotificationPanel panel) {
+ final List allText = Lists.newArrayList();
+ final Collection labels = robot().finder().findAll(panel, JLabelMatcher.any().andShowing());
+ for (final JLabel label : labels) {
+ String text = execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected String executeInEDT() throws Throwable {
+ return label.getText();
+ }
+ });
+ if (isNotEmpty(text)) {
+ allText.add(text);
+ }
+ }
+ return allText;
+ }
+
+ @NotNull
+ public IdeSettingsDialogFixture openIdeSettings() {
+ // Using invokeLater because we are going to show a *modal* dialog via API (instead of clicking a button, for example.) If we use
+ // GuiActionRunner the test will hang until the modal dialog is closed.
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Project project = getProject();
+ ShowSettingsUtil.getInstance().showSettingsDialog(project, ShowSettingsUtilImpl.getConfigurableGroups(project, true));
+ }
+ });
+ return IdeSettingsDialogFixture.find(robot());
+ }
+
+
+ @NotNull
+ public RunConfigurationsDialogFixture invokeRunConfigurationsDialog() {
+ invokeMenuPath("Run", "Edit Configurations...");
+ return RunConfigurationsDialogFixture.find(robot());
+ }
+
+ @NotNull
+ public InspectionsFixture inspectCode() {
+ invokeMenuPath("Analyze", "Inspect Code...");
+
+ //final Ref wrapperRef = new Ref();
+ JDialog dialog = robot().finder().find(new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ return "Specify Inspection Scope".equals(dialog.getTitle());
+ }
+ });
+ JButton button = robot().finder().find(dialog, JButtonMatcher.withText("OK").andShowing());
+ robot().click(button);
+
+ final InspectionTree tree = waitUntilFound(robot(), new GenericTypeMatcher(InspectionTree.class) {
+ @Override
+ protected boolean isMatching(@NotNull InspectionTree component) {
+ return true;
+ }
+ });
+
+ return new InspectionsFixture(robot(), getProject(), tree);
+ }
+
+ @NotNull
+ public ProjectViewFixture getProjectView() {
+ return new ProjectViewFixture(getProject(), robot());
+ }
+
+ @NotNull
+ public Project getProject() {
+ Project project = target().getProject();
+ assertNotNull(project);
+ return project;
+ }
+
+ public void closeProject() {
+ execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ closeAndDispose(getProject());
+ RecentProjectsManager.getInstance().updateLastProjectPath();
+ WelcomeFrame.showIfNoProjectOpened();
+ }
+ });
+ pause(new Condition("Waiting for 'Welcome' page to show up") {
+ @Override
+ public boolean test() {
+ for (Frame frame : Frame.getFrames()) {
+ if (frame == WelcomeFrame.getInstance() && frame.isShowing()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ }
+
+ @NotNull
+ public MessagesFixture findMessageDialog(@NotNull String title) {
+ return MessagesFixture.findByTitle(robot(), target(), title);
+ }
+
+
+
+ @NotNull
+ public FindDialogFixture invokeFindInPathDialog() {
+ invokeMenuPath("Edit", "Find", "Find in Path...");
+ return FindDialogFixture.find(robot());
+ }
+
+ @NotNull
+ public FindToolWindowFixture getFindToolWindow() {
+ return new FindToolWindowFixture(this);
+ }
+
+ //@NotNull
+ //public GradleBuildModelFixture parseBuildFileForModule(@NotNull String moduleName, boolean openInEditor) {
+ // Module module = getModule(moduleName);
+ // return parseBuildFile(module, openInEditor);
+ //}
+ //
+ //@NotNull
+ //public GradleBuildModelFixture parseBuildFile(@NotNull Module module, boolean openInEditor) {
+ // VirtualFile buildFile = getGradleBuildFile(module);
+ // assertNotNull(buildFile);
+ // return parseBuildFile(buildFile, openInEditor);
+ //}
+ //
+ //@NotNull
+ //public GradleBuildModelFixture parseBuildFile(@NotNull final VirtualFile buildFile, boolean openInEditor) {
+ // if (openInEditor) {
+ // getEditor().open(buildFile, Tab.DEFAULT).getCurrentFile();
+ // }
+ // final Ref buildModelRef = new Ref();
+ // new ReadAction() {
+ // @Override
+ // protected void run(@NotNull Result result) throws Throwable {
+ // buildModelRef.set(GradleBuildModel.parseBuildFile(buildFile, getProject()));
+ // }
+ // }.execute();
+ // GradleBuildModel buildModel = buildModelRef.get();
+ // assertNotNull(buildModel);
+ // return new GradleBuildModelFixture(buildModel);
+ //}
+
+ private static class NoOpDisposable implements Disposable {
+ @Override
+ public void dispose() {
+ }
+ }
+
+ public void selectApp(@NotNull String appName) {
+ final ActionButtonFixture runButton = findRunApplicationButton();
+ Container actionToolbarContainer = execute(new GuiQuery() {
+ @Override
+ protected Container executeInEDT() throws Throwable {
+ return runButton.target().getParent();
+ }
+ });
+ assertNotNull(actionToolbarContainer);
+
+ ComboBoxActionFixture comboBoxActionFixture = ComboBoxActionFixture.findComboBox(robot(), actionToolbarContainer);
+ comboBoxActionFixture.selectItem(appName);
+ robot().pressAndReleaseKey(KeyEvent.VK_ENTER);
+ robot().waitForIdle();
+ }
+
+ /////////////////////////////////////////////////////////////////
+ //// Methods to help control debugging under a test. ///////
+ /////////////////////////////////////////////////////////////////
+
+ public void resumeProgram() {
+ invokeMenuPathOnRobotIdle(this, "Run", "Resume Program");
+ }
+
+ public void stepOver() {
+ invokeMenuPathOnRobotIdle(this, "Run", "Step Over");
+ }
+
+ public void stepInto() {
+ invokeMenuPathOnRobotIdle(this, "Run", "Step Into");
+ }
+
+ public void stepOut() {
+ invokeMenuPathOnRobotIdle(this, "Run", "Step Out");
+ }
+
+ /**
+ * Toggles breakpoints at the line numbers in {@code lines} of the source file with basename {@code fileBaseName}. This will work only
+ * if the fileBasename is unique in the project that's open on Android Studio.
+ */
+ public void toggleBreakPoints(String fileBasename, int[] lines) {
+ // We open the file twice to bring the editor into focus. Idea 1.15 has this bug where opening a file doesn't automatically bring its
+ // editor window into focus.
+ openFile(this, fileBasename);
+ openFile(this, fileBasename);
+ for (int line : lines) {
+ navigateToLine(this, line);
+ invokeMenuPathOnRobotIdle(this, "Run", "Toggle Line Breakpoint");
+ }
+ }
+
+ // Recursively prints out the debugger tree rooted at {@code node} into {@code builder} with an indent of
+ // "{@code level} * {@code numIndentSpaces}" whitespaces. Each node is printed on a separate line. The indent level for every child node
+ // is 1 more than their parent.
+ private static void printNode(XDebuggerTreeNode node, StringBuilder builder, int level, int numIndentSpaces) {
+ int numIndent = level;
+ if (builder.length() > 0) {
+ builder.append(System.getProperty("line.separator"));
+ }
+ for (int i = 0; i < level * numIndentSpaces; ++i) {
+ builder.append(' ');
+ }
+ builder.append(node.getText().toString());
+ Enumeration children = node.children();
+ while (children.hasMoreElements()) {
+ printNode(children.nextElement(), builder, level + 1, numIndentSpaces);
+ }
+ }
+
+ /**
+ * Prints out the debugger tree rooted at {@code root}.
+ */
+ @NotNull
+ public static String printDebuggerTree(XDebuggerTreeNode root) {
+ StringBuilder builder = new StringBuilder();
+ printNode(root, builder, 0, 2);
+ return builder.toString();
+ }
+
+ @NotNull
+ private static String[] debuggerTreeRootToChildrenTexts(XDebuggerTreeNode treeRoot) {
+ List extends TreeNode> children = treeRoot.getChildren();
+ String[] childrenTexts = new String[children.size()];
+ int i = 0;
+ for (TreeNode child : children) {
+ childrenTexts[i] = ((XDebuggerTreeNode)child).getText().toString();
+ ++i;
+ }
+ return childrenTexts;
+ }
+
+ /**
+ * Returns the subset of {@code expectedPatterns} which do not match any of the children (just the first level children, not recursive) of
+ * {@code treeRoot} .
+ */
+ @NotNull
+ public static List getUnmatchedTerminalVariableValues(String[] expectedPatterns, XDebuggerTreeNode treeRoot) {
+ String[] childrenTexts = debuggerTreeRootToChildrenTexts(treeRoot);
+ List unmatchedPatterns = Lists.newArrayList();
+ for (String expectedPattern : expectedPatterns) {
+ boolean matched = false;
+ for (String childText : childrenTexts) {
+ if (childText.matches(expectedPattern)) {
+ matched = true;
+ break;
+ }
+ }
+ if (!matched) {
+ unmatchedPatterns.add(expectedPattern);
+ }
+ }
+ return unmatchedPatterns;
+ }
+
+ /**
+ * Returns the appropriate pattern to look for a variable named {@code name} with the type {@code type} and value {@code value} appearing
+ * in the Variables window in Android Studio.
+ */
+ @NotNull
+ public static String variableToSearchPattern(String name, String type, String value) {
+ return String.format("%s = \\{%s\\} %s", name, type, value);
+ }
+
+ public boolean verifyVariablesAtBreakpoint(String[] expectedVariablePatterns, String debugConfigName, long tryUntilMillis) {
+ DebugToolWindowFixture debugToolWindowFixture = new DebugToolWindowFixture(this);
+ final ExecutionToolWindowFixture.ContentFixture contentFixture = debugToolWindowFixture.findContent(debugConfigName);
+
+ contentFixture.clickDebuggerTreeRoot();
+ // Wait for the debugger tree to appear.
+ pause(new Condition("Looking for debugger tree.") {
+ @Override
+ public boolean test() {
+ return contentFixture.getDebuggerTreeRoot() != null;
+ }
+ }, Timeout.timeout(tryUntilMillis));
+
+ // Get the debugger tree and print it.
+ XDebuggerTreeNode debuggerTreeRoot = contentFixture.getDebuggerTreeRoot();
+ if (debuggerTreeRoot == null) {
+ return false;
+ }
+
+ List unmatchedPatterns = getUnmatchedTerminalVariableValues(expectedVariablePatterns, debuggerTreeRoot);
+ return unmatchedPatterns.isEmpty();
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/IdeSettingsDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/IdeSettingsDialogFixture.java
new file mode 100644
index 000000000000..ef8c06a6eb93
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/IdeSettingsDialogFixture.java
@@ -0,0 +1,97 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.google.common.collect.Lists;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurableGroup;
+import com.intellij.openapi.options.newEditor.SettingsDialog;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.ui.treeStructure.CachingSimpleNode;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.reflect.core.Reflection.field;
+import static org.junit.Assert.assertNotNull;
+
+public class IdeSettingsDialogFixture extends IdeaDialogFixture {
+ @NotNull
+ public static IdeSettingsDialogFixture find(@NotNull Robot robot) {
+ return new IdeSettingsDialogFixture(robot, find(robot, SettingsDialog.class, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ String expectedTitle = SystemInfo.isMac ? "Preferences" : "Settings";
+ return expectedTitle.equals(dialog.getTitle()) && dialog.isShowing();
+ }
+ }));
+ }
+
+ private IdeSettingsDialogFixture(@NotNull Robot robot, @NotNull DialogAndWrapper dialogAndWrapper) {
+ super(robot, dialogAndWrapper);
+ }
+
+ @NotNull
+ public List getProjectSettingsNames() {
+ List names = Lists.newArrayList();
+ JPanel optionsEditor = field("myEditor").ofType(JPanel.class).in(getDialogWrapper()).get();
+ assertNotNull(optionsEditor);
+
+ List trees = findComponentsOfType(optionsEditor, "com.intellij.openapi.options.newEditor.SettingsTreeView");
+ assertThat(trees).hasSize(1);
+ JComponent tree = trees.get(0);
+
+ CachingSimpleNode root = field("myRoot").ofType(CachingSimpleNode.class).in(tree).get();
+ assertNotNull(root);
+
+ ConfigurableGroup[] groups = field("myGroups").ofType(ConfigurableGroup[].class).in(root).get();
+ assertNotNull(groups);
+ for (ConfigurableGroup current : groups) {
+ Configurable[] configurables = current.getConfigurables();
+ for (Configurable configurable : configurables) {
+ names.add(configurable.getDisplayName());
+ }
+ }
+ return names;
+ }
+
+ @NotNull
+ private static List findComponentsOfType(@NotNull JComponent parent, @NotNull String typeName) {
+ List result = Lists.newArrayList();
+ findComponentsOfType(typeName, result, parent);
+ return result;
+ }
+
+ private static void findComponentsOfType(@NotNull String typeName, @NotNull List result, @Nullable JComponent parent) {
+ if (parent == null) {
+ return;
+ }
+ if (parent.getClass().getName().equals(typeName)) {
+ result.add(parent);
+ }
+ for (Component c : parent.getComponents()) {
+ if (c instanceof JComponent) {
+ findComponentsOfType(typeName, result, (JComponent)c);
+ }
+ }
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/IdeaDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/IdeaDialogFixture.java
new file mode 100644
index 000000000000..2f93ae7898c2
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/IdeaDialogFixture.java
@@ -0,0 +1,120 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Ref;
+import org.fest.reflect.exception.ReflectionError;
+import org.fest.reflect.reference.TypeRef;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.ContainerFixture;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.lang.ref.WeakReference;
+
+import static com.intellij.tests.gui.framework.GuiTests.findAndClickCancelButton;
+import static com.intellij.tests.gui.framework.GuiTests.waitUntilFound;
+import static junit.framework.Assert.assertNotNull;
+import static org.fest.reflect.core.Reflection.field;
+
+public abstract class IdeaDialogFixture extends ComponentFixture implements ContainerFixture {
+ @NotNull private final T myDialogWrapper;
+
+ @Nullable
+ protected static T getDialogWrapperFrom(@NotNull JDialog dialog, Class dialogWrapperType) {
+ try {
+ WeakReference dialogWrapperRef = field("myDialogWrapper").ofType(new TypeRef>() {})
+ .in(dialog)
+ .get();
+ assertNotNull(dialogWrapperRef);
+ DialogWrapper wrapper = dialogWrapperRef.get();
+ if (dialogWrapperType.isInstance(wrapper)) {
+ return dialogWrapperType.cast(wrapper);
+ }
+ }
+ catch (ReflectionError ignored) {
+ }
+ return null;
+ }
+
+ public static class DialogAndWrapper {
+ public final JDialog dialog;
+ public final T wrapper;
+
+ public DialogAndWrapper(@NotNull JDialog dialog, @NotNull T wrapper) {
+ this.dialog = dialog;
+ this.wrapper = wrapper;
+ }
+ }
+
+ @NotNull
+ public static DialogAndWrapper find(@NotNull Robot robot, @NotNull final Class clz) {
+ return find(robot, clz, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog component) {
+ return component.isShowing();
+ }
+ });
+ }
+
+ @NotNull
+ public static DialogAndWrapper find(@NotNull Robot robot, @NotNull final Class clz,
+ @NotNull final GenericTypeMatcher matcher) {
+ final Ref wrapperRef = new Ref();
+ JDialog dialog = waitUntilFound(robot, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ if (matcher.matches(dialog)) {
+ T wrapper = getDialogWrapperFrom(dialog, clz);
+ if (wrapper != null) {
+ wrapperRef.set(wrapper);
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ return new DialogAndWrapper(dialog, wrapperRef.get());
+ }
+
+ protected IdeaDialogFixture(@NotNull Robot robot, @NotNull JDialog target, @NotNull T dialogWrapper) {
+ super(IdeaDialogFixture.class, robot, target);
+ myDialogWrapper = dialogWrapper;
+ }
+
+ protected IdeaDialogFixture(@NotNull Robot robot, @NotNull DialogAndWrapper dialogAndWrapper) {
+ this(robot, dialogAndWrapper.dialog, dialogAndWrapper.wrapper);
+ }
+
+ @NotNull
+ protected T getDialogWrapper() {
+ return myDialogWrapper;
+ }
+
+ public void clickCancel() {
+ // Grab focus in case it is not automatically done by the window manager, e.g. 9wm
+ focus();
+
+ findAndClickCancelButton(this);
+ }
+
+ public void close() {
+ robot().close(target());
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/InputDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/InputDialogFixture.java
new file mode 100644
index 000000000000..465f32320cd7
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/InputDialogFixture.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Ref;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.core.matcher.JTextComponentMatcher;
+import org.fest.swing.fixture.JTextComponentFixture;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+
+import static com.intellij.tests.gui.framework.GuiTests.findAndClickOkButton;
+import static com.intellij.tests.gui.framework.GuiTests.waitUntilFound;
+import static org.junit.Assert.assertNotNull;
+
+public class InputDialogFixture extends IdeaDialogFixture {
+ @NotNull
+ public static InputDialogFixture findByTitle(@NotNull Robot robot, @NotNull final String title) {
+ final Ref wrapperRef = new Ref();
+ JDialog dialog = waitUntilFound(robot, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ if (!title.equals(dialog.getTitle()) || !dialog.isShowing()) {
+ return false;
+ }
+ DialogWrapper wrapper = getDialogWrapperFrom(dialog, DialogWrapper.class);
+ if (wrapper != null) {
+ String typeName = Messages.class.getName() + "$InputDialog";
+ if (typeName.equals(wrapper.getClass().getName())) {
+ wrapperRef.set(wrapper);
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ return new InputDialogFixture(robot, dialog, wrapperRef.get());
+ }
+
+ public void enterTextAndClickOk(@NotNull String text) {
+ JTextComponent input = robot().finder().find(target(), JTextComponentMatcher.any());
+ assertNotNull(input);
+ JTextComponentFixture inputFixture = new JTextComponentFixture(robot(), input);
+ inputFixture.enterText(text);
+ findAndClickOkButton(this);
+ }
+
+ private InputDialogFixture(@NotNull Robot robot, @NotNull JDialog target, @NotNull DialogWrapper dialogWrapper) {
+ super(robot, target, dialogWrapper);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/InspectionsFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/InspectionsFixture.java
new file mode 100644
index 000000000000..c8cfcabe69db
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/InspectionsFixture.java
@@ -0,0 +1,83 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.google.common.collect.Lists;
+import com.intellij.codeInspection.ui.InspectionTree;
+import com.intellij.codeInspection.ui.InspectionTreeNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindowId;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiQuery;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.fest.swing.edt.GuiActionRunner.execute;
+
+/**
+ * Fixture for the Inspections window in the IDE
+ */
+public class InspectionsFixture extends ToolWindowFixture {
+ private final InspectionTree myTree;
+
+ public InspectionsFixture(@NotNull Robot robot, @NotNull Project project, InspectionTree tree) {
+ super(ToolWindowId.INSPECTION, project, robot);
+ myTree = tree;
+ }
+
+ public String getResults() {
+ activate();
+ waitUntilIsVisible();
+
+ return execute(new GuiQuery() {
+ @Override
+ @Nullable
+ protected String executeInEDT() throws Throwable {
+ StringBuilder sb = new StringBuilder();
+ InspectionsFixture.describe(myTree.getRoot(), sb, 0);
+ return sb.toString();
+ }
+ });
+ }
+
+ public static void describe(@NotNull InspectionTreeNode node, @NotNull StringBuilder sb, int depth) {
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ sb.append(node.toString());
+ sb.append("\n");
+
+ // The exact order of the results sometimes varies so sort the children alphabetically
+ // instead to ensure stable test output
+ List children = Lists.newArrayListWithExpectedSize(node.getChildCount());
+ for (int i = 0, n = node.getChildCount(); i < n; i++) {
+ children.add((InspectionTreeNode)node.getChildAt(i));
+ }
+ Collections.sort(children, new Comparator() {
+ @Override
+ public int compare(InspectionTreeNode node1, InspectionTreeNode node2) {
+ return node1.toString().compareTo(node2.toString());
+ }
+ });
+ for (InspectionTreeNode child : children) {
+ describe(child, sb, depth + 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/JComponentFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/JComponentFixture.java
new file mode 100644
index 000000000000..2ed098eeadd5
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/JComponentFixture.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import org.fest.swing.core.Robot;
+import org.fest.swing.driver.JComponentDriver;
+import org.fest.swing.fixture.AbstractJComponentFixture;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public abstract class JComponentFixture extends AbstractJComponentFixture {
+ public JComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @NotNull Class extends C> type) {
+ super(selfType, robot, type);
+ }
+
+ public JComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @Nullable String name, @NotNull Class extends C> type) {
+ super(selfType, robot, name, type);
+ }
+
+ public JComponentFixture(@NotNull Class selfType, @NotNull Robot robot, @NotNull C target) {
+ super(selfType, robot, target);
+ }
+
+ @Override
+ @NotNull
+ protected JComponentDriver createDriver(@NotNull Robot robot) {
+ return new JComponentDriver(robot);
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/LibraryFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/LibraryFixture.java
new file mode 100644
index 000000000000..7a2efc145364
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/LibraryFixture.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.roots.JavadocOrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import org.jetbrains.annotations.NotNull;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class LibraryFixture {
+ @NotNull private final Library myLibrary;
+
+ LibraryFixture(@NotNull Library library) {
+ myLibrary = library;
+ }
+
+ @NotNull
+ public LibraryFixture requireJavadocUrls(@NotNull String... urls) {
+ String[] actualUrls = myLibrary.getUrls(JavadocOrderRootType.getInstance());
+ assertThat(actualUrls).as("Javadoc URLs").containsOnly(urls);
+ return this;
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/MenuFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/MenuFixture.java
new file mode 100644
index 000000000000..7db83502bb03
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/MenuFixture.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.google.common.collect.Lists;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.wm.impl.IdeFrameImpl;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.util.Lists.newArrayList;
+import static org.junit.Assert.assertNotNull;
+
+class MenuFixture {
+ @NotNull private final Robot myRobot;
+ @NotNull private final IdeFrameImpl myContainer;
+
+ MenuFixture(@NotNull Robot robot, @NotNull IdeFrameImpl container) {
+ myRobot = robot;
+ myContainer = container;
+ }
+
+ /**
+ * Invokes an action by menu path
+ *
+ * @param path the series of menu names, e.g. {@link invokeActionByMenuPath("Build", "Make Project ")}
+ */
+ void invokeMenuPath(@NotNull String... path) {
+ JMenuItem menuItem = findActionMenuItem(false, path);
+ myRobot.click(menuItem);
+ }
+
+ /**
+ * Invokes an action by menu path (where each segment is a regular expression). This is particularly
+ * useful when the menu items can change dynamically, such as the labels of Undo actions, Run actions,
+ * etc.
+ *
+ * @param path the series of menu name regular expressions, e.g. {@link invokeActionByMenuPath("Build", "Make( Project)?")}
+ */
+ void invokeMenuPathRegex(@NotNull String... path) {
+ JMenuItem menuItem = findActionMenuItem(true, path);
+ myRobot.click(menuItem);
+ }
+
+ @NotNull
+ private JMenuItem findActionMenuItem(final boolean pathIsRegex, @NotNull String... path) {
+ assertThat(path).isNotEmpty();
+ int segmentCount = path.length;
+
+ // We keep the list of previously found pop-up menus, so we don't look for menu items in the same pop-up more than once.
+ List previouslyFoundPopups = Lists.newArrayList();
+
+ Container root = myContainer;
+ for (int i = 0; i < segmentCount; i++) {
+ final String segment = path[i];
+ assertNotNull(root);
+ JMenuItem found = myRobot.finder().find(root, new GenericTypeMatcher(JMenuItem.class) {
+ @Override
+ protected boolean isMatching(@NotNull JMenuItem menuItem) {
+ return pathIsRegex ? menuItem.getText().matches(segment) : segment.equals(menuItem.getText());
+ }
+ });
+ if (root instanceof JPopupMenu) {
+ previouslyFoundPopups.add((JPopupMenu)root);
+ }
+ if (i < segmentCount - 1) {
+ myRobot.click(found);
+ List showingPopupMenus = findShowingPopupMenus(i + 1);
+ showingPopupMenus.removeAll(previouslyFoundPopups);
+ assertThat(showingPopupMenus).hasSize(1);
+ root = showingPopupMenus.get(0);
+ continue;
+ }
+ return found;
+ }
+ throw new AssertionError("Menu item with path " + Arrays.toString(path) + " should have been found already");
+ }
+
+ @NotNull
+ private List findShowingPopupMenus(final int expectedCount) {
+ final Ref> ref = new Ref>();
+ Pause.pause(new Condition("waiting for " + expectedCount + " JPopupMenus to show up") {
+ @Override
+ public boolean test() {
+ List popupMenus = newArrayList(myRobot.finder().findAll(new GenericTypeMatcher(JPopupMenu.class) {
+ @Override
+ protected boolean isMatching(@NotNull JPopupMenu popupMenu) {
+ return popupMenu.isShowing();
+ }
+ }));
+ boolean allFound = popupMenus.size() == expectedCount;
+ if (allFound) {
+ ref.set(popupMenus);
+ }
+ return allFound;
+ }
+ });
+ List popupMenus = ref.get();
+ assertThat(popupMenus).isNotNull().hasSize(expectedCount);
+ return popupMenus;
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/MessageDialogFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/MessageDialogFixture.java
new file mode 100644
index 000000000000..556fdf449ba6
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/MessageDialogFixture.java
@@ -0,0 +1,72 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Ref;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.edt.GuiQuery;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static com.intellij.tests.gui.framework.GuiTests.waitUntilFound;
+import static org.fest.swing.edt.GuiActionRunner.execute;
+
+class MessageDialogFixture extends IdeaDialogFixture implements MessagesFixture.Delegate {
+ @NotNull
+ static MessageDialogFixture findByTitle(@NotNull Robot robot, @NotNull final String title) {
+ final Ref wrapperRef = new Ref();
+ JDialog dialog = waitUntilFound(robot, new GenericTypeMatcher(JDialog.class) {
+ @Override
+ protected boolean isMatching(@NotNull JDialog dialog) {
+ if (!title.equals(dialog.getTitle()) || !dialog.isShowing()) {
+ return false;
+ }
+ DialogWrapper wrapper = getDialogWrapperFrom(dialog, DialogWrapper.class);
+ if (wrapper != null) {
+ String typeName = Messages.class.getName() + "$MessageDialog";
+ if (typeName.equals(wrapper.getClass().getName())) {
+ wrapperRef.set(wrapper);
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ return new MessageDialogFixture(robot, dialog, wrapperRef.get());
+ }
+
+ private MessageDialogFixture(@NotNull Robot robot, @NotNull JDialog target, @NotNull DialogWrapper dialogWrapper) {
+ super(robot, target, dialogWrapper);
+ }
+
+ @Override
+ @NotNull
+ public String getMessage() {
+ final JTextPane textPane = robot().finder().findByType(target(), JTextPane.class);
+ //noinspection ConstantConditions
+ return execute(new GuiQuery() {
+ @Override
+ protected String executeInEDT() throws Throwable {
+ return nullToEmpty(textPane.getText());
+ }
+ });
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/MessagesFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/MessagesFixture.java
new file mode 100644
index 000000000000..848fb7190db6
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/MessagesFixture.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 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.intellij.tests.gui.fixtures;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.ui.messages.SheetController;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.fixture.ContainerFixture;
+import org.fest.swing.fixture.JPanelFixture;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+
+import org.fest.swing.core.Robot;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static com.intellij.openapi.util.JDOMUtil.loadDocument;
+import static com.intellij.tests.gui.framework.GuiTests.*;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.reflect.core.Reflection.field;
+import static org.junit.Assert.assertNotNull;
+
+public class MessagesFixture {
+ @NotNull private final ContainerFixture extends Container> myDelegate;
+
+ @NotNull
+ public static MessagesFixture findByTitle(@NotNull Robot robot, @NotNull Container root, @NotNull String title) {
+ if (Messages.canShowMacSheetPanel()) {
+ return new MessagesFixture(findMacSheetByTitle(robot, root, title));
+ }
+ MessageDialogFixture dialog = MessageDialogFixture.findByTitle(robot, title);
+ return new MessagesFixture(dialog);
+ }
+
+ private MessagesFixture(@NotNull ContainerFixture extends Container> delegate) {
+ myDelegate = delegate;
+ }
+
+ @NotNull
+ public MessagesFixture clickOk() {
+ findAndClickOkButton(myDelegate);
+ return this;
+ }
+
+
+
+ @NotNull
+ public MessagesFixture clickYes() {
+ return click("Yes");
+ }
+
+ @NotNull
+ public MessagesFixture click(@NotNull String text) {
+ findAndClickButton(myDelegate, text);
+ return this;
+ }
+
+ @NotNull
+ public MessagesFixture requireMessageContains(@NotNull String message) {
+ String actual = ((Delegate)myDelegate).getMessage();
+ assertThat(actual).contains(message);
+ return this;
+ }
+
+ public void clickCancel() {
+ findAndClickCancelButton(myDelegate);
+ }
+
+ @NotNull
+ static JPanelFixture findMacSheetByTitle(@NotNull Robot robot, @NotNull Container root, @NotNull String title) {
+ JPanel sheetPanel = waitUntilFound(robot, root, new GenericTypeMatcher(JPanel.class) {
+ @Override
+ protected boolean isMatching(@NotNull JPanel panel) {
+ if (panel.getClass().getName().startsWith(SheetController.class.getName()) && panel.isShowing()) {
+ SheetController controller = findSheetController(panel);
+ JPanel sheetPanel = field("mySheetPanel").ofType(JPanel.class).in(controller).get();
+ if (sheetPanel == panel) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+ String sheetTitle = getTitle(sheetPanel, robot);
+ assertThat(sheetTitle).as("Sheet title").isEqualTo(title);
+
+ return new MacSheetPanelFixture(robot, sheetPanel);
+ }
+
+ @Nullable
+ private static String getTitle(@NotNull JPanel sheetPanel, @NotNull Robot robot) {
+ final JEditorPane messageTextPane = getMessageTextPane(sheetPanel);
+
+ JEditorPane titleTextPane = robot.finder().find(sheetPanel, new GenericTypeMatcher(JEditorPane.class) {
+ @Override
+ protected boolean isMatching(@NotNull JEditorPane editorPane) {
+ return editorPane != messageTextPane;
+ }
+ });
+
+ return getHtmlBody(titleTextPane.getText());
+ }
+
+ @Nullable
+ public T find(GenericTypeMatcher matcher) {
+ return myDelegate.robot().finder().find(myDelegate.target(), matcher);
+ }
+
+ interface Delegate {
+ @NotNull String getMessage();
+ }
+
+ private static class MacSheetPanelFixture extends JPanelFixture implements Delegate {
+ public MacSheetPanelFixture(@NotNull Robot robot, @NotNull JPanel target) {
+ super(robot, target);
+ }
+
+ @Override
+ @NotNull
+ public String getMessage() {
+ JEditorPane messageTextPane = getMessageTextPane(target());
+ String text = getHtmlBody(messageTextPane.getText());
+ return nullToEmpty(text);
+ }
+ }
+
+ @NotNull
+ private static JEditorPane getMessageTextPane(@NotNull JPanel sheetPanel) {
+ SheetController sheetController = findSheetController(sheetPanel);
+ JEditorPane messageTextPane = field("messageTextPane").ofType(JEditorPane.class).in(sheetController).get();
+ assertNotNull(messageTextPane);
+ return messageTextPane;
+ }
+
+ @NotNull
+ private static SheetController findSheetController(@NotNull JPanel sheetPanel) {
+ SheetController sheetController = field("this$0").ofType(SheetController.class).in(sheetPanel).get();
+ assertNotNull(sheetController);
+ return sheetController;
+ }
+
+ @Nullable
+ private static String getHtmlBody(@NotNull String html) {
+ try {
+ Document document = loadDocument(html);
+ Element rootElement = document.getRootElement();
+ String sheetTitle = rootElement.getChild("body").getText();
+ return sheetTitle.replace("\n", "").trim();
+ }
+ catch (Throwable e) {
+ Logger.getInstance(MessagesFixture.class).info("Failed to parse HTML '" + html + "'", e);
+ }
+ return null;
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/MessagesToolWindowFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/MessagesToolWindowFixture.java
new file mode 100644
index 000000000000..67920dc5cecd
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/MessagesToolWindowFixture.java
@@ -0,0 +1,375 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+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() {
+ @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("([^<]+)");
+
+ @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 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("");
+ assertThat(startBodyIndex).isGreaterThanOrEqualTo(0);
+
+ int endBodyIndex = html.indexOf("");
+ assertThat(endBodyIndex).isGreaterThan(startBodyIndex);
+
+ String body = html.substring(startBodyIndex + 6 /* 6 = length of '' */, endBodyIndex);
+ List lines = Splitter.on('\n').omitEmptyStrings().trimResults().splitToList(body);
+ body = Joiner.on(' ').join(lines);
+
+ return body;
+ }
+
+ @NotNull
+ private Pair 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() {
+ @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() {
+ @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));
+ }
+ }
+}
diff --git a/community-tests/src/com/intellij/tests/gui/fixtures/ProjectViewFixture.java b/community-tests/src/com/intellij/tests/gui/fixtures/ProjectViewFixture.java
new file mode 100644
index 000000000000..067ddb32b2fe
--- /dev/null
+++ b/community-tests/src/com/intellij/tests/gui/fixtures/ProjectViewFixture.java
@@ -0,0 +1,282 @@
+/*
+ * 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.intellij.tests.gui.fixtures;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.ide.projectView.ProjectViewNode;
+import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
+import com.intellij.ide.projectView.impl.nodes.ExternalLibrariesNode;
+import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
+import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode;
+import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.ide.util.treeView.AbstractTreeStructure;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.JdkOrderEntry;
+import com.intellij.openapi.roots.LibraryOrSdkOrderEntry;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.util.ui.tree.TreeUtil;
+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.fest.swing.timing.Condition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.intellij.tests.gui.framework.GuiTests.SHORT_TIMEOUT;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.reflect.core.Reflection.field;
+import static org.fest.swing.timing.Pause.pause;
+import static org.junit.Assert.assertNotNull;
+
+public class ProjectViewFixture extends ToolWindowFixture {
+ ProjectViewFixture(@NotNull Project project, @NotNull Robot robot) {
+ super("Project", project, robot);
+ }
+
+ @NotNull
+ public PaneFixture selectProjectPane() {
+ activate();
+ final ProjectView projectView = ProjectView.getInstance(myProject);
+ pause(new Condition("Project view is initialized") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return field("isInitialized").ofType(boolean.class).in(projectView).get();
+ }
+ }, SHORT_TIMEOUT);
+
+ final String id = "ProjectPane";
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ projectView.changeView(id);
+ }
+ });
+ return new PaneFixture(projectView.getProjectViewPaneById(id));
+ }
+
+ @NotNull
+ public PaneFixture selectAndroidPane() {
+ activate();
+ final ProjectView projectView = ProjectView.getInstance(myProject);
+ pause(new Condition("Project view is initialized") {
+ @Override
+ public boolean test() {
+ //noinspection ConstantConditions
+ return field("isInitialized").ofType(boolean.class).in(projectView).get();
+ }
+ }, SHORT_TIMEOUT);
+
+ final String id = "AndroidView";
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ projectView.changeView(id);
+ }
+ });
+ return new PaneFixture(projectView.getProjectViewPaneById(id));
+ }
+
+ public static class PaneFixture {
+ @NotNull private final AbstractProjectViewPane myPane;
+
+ PaneFixture(@NotNull AbstractProjectViewPane pane) {
+ myPane = pane;
+ }
+
+ @NotNull
+ public PaneFixture expand() {
+ GuiActionRunner.execute(new GuiTask() {
+ @Override
+ protected void executeInEDT() throws Throwable {
+ TreeUtil.expandAll(myPane.getTree());
+ }
+ });
+ return this;
+ }
+
+ @NotNull
+ private AbstractTreeStructure getTreeStructure() {
+ final AtomicReference treeStructureRef = new AtomicReference();
+ pause(new Condition("Tree Structure to be built") {
+ @Override
+ public boolean test() {
+ AbstractTreeStructure treeStructure = GuiActionRunner.execute(new GuiQuery() {
+ @Override
+ protected AbstractTreeStructure executeInEDT() throws Throwable {
+ try {
+ return myPane.getTreeBuilder().getTreeStructure();
+ }
+ catch (NullPointerException e) {
+ // expected;
+ }
+ return null;
+ }
+ });
+ treeStructureRef.set(treeStructure);
+ return treeStructure != null;
+ }
+ }, SHORT_TIMEOUT);
+
+ return treeStructureRef.get();
+ }
+
+ @NotNull
+ public NodeFixture findExternalLibrariesNode() {
+ final AbstractTreeStructure treeStructure = getTreeStructure();
+
+ ExternalLibrariesNode node = GuiActionRunner.execute(new GuiQuery() {
+ @Nullable
+ @Override
+ protected ExternalLibrariesNode executeInEDT() throws Throwable {
+ Object[] childElements = treeStructure.getChildElements(treeStructure.getRootElement());
+ for (Object child : childElements) {
+ if (child instanceof ExternalLibrariesNode) {
+ return (ExternalLibrariesNode)child;
+ }
+ }
+ return null;
+ }
+ });
+ if (node != null) {
+ return new NodeFixture(node, treeStructure);
+ }
+ throw new AssertionError("Unable to find 'External Libraries' node");
+ }
+
+ public void selectByPath(@NotNull final String... paths) {
+ final AbstractTreeStructure treeStructure = getTreeStructure();
+
+ final PsiDirectoryNode node = GuiActionRunner.execute(new GuiQuery() {
+ @Nullable
+ @Override
+ protected PsiDirectoryNode executeInEDT() throws Throwable {
+ Object root = treeStructure.getRootElement();
+ final List