diff --git a/.idea/modules.xml b/.idea/modules.xml index 8f16bb372d09..5201ba96b116 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -59,6 +59,7 @@ + diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index e093416f9e51..2298a387ff7a 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -160,9 +160,10 @@ serviceImplementation="git4idea.remote.GitRememberedInputs"/> - + diff --git a/plugins/git4idea/src/git4idea/DialogManager.java b/plugins/git4idea/src/git4idea/DialogManager.java new file mode 100644 index 000000000000..585862165c0a --- /dev/null +++ b/plugins/git4idea/src/git4idea/DialogManager.java @@ -0,0 +1,25 @@ +package git4idea; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.NotNull; + +/** + * Use {@link DialogManager#show(DialogWrapper) DialogManager.show(DialogWrapper)} instead of {@link DialogWrapper#show()} + * to make the code testable: + * in the test environment such calls will be transferred to the TestDialogManager and can be handled by tests; + * in the production environment they will be simply delegated to DialogWrapper#show(). + * + * @author Kirill Likhodedov + */ +public class DialogManager { + + public static void show(@NotNull DialogWrapper dialog) { + ServiceManager.getService(DialogManager.class).showDialog(dialog); + } + + protected void showDialog(@NotNull DialogWrapper dialog) { + dialog.show(); + } + +} diff --git a/plugins/git4idea/testFramework/META-INF/plugin.xml b/plugins/git4idea/testFramework/META-INF/plugin.xml index 90ca43720e5c..6ecf51797de2 100644 --- a/plugins/git4idea/testFramework/META-INF/plugin.xml +++ b/plugins/git4idea/testFramework/META-INF/plugin.xml @@ -10,6 +10,9 @@ + { +public interface TestDialogHandler { + /** - * Do something with dialog fields and return the exit code - as if user pressed one of exit buttons. + * Do something with the dialog (modify its instance fields, for example) + * and return the exit code - as if user pressed one of exit buttons. + * * @param dialog dialog to be handled. - * @return DialogWrapper exit code, for example, {@link com.intellij.openapi.ui.DialogWrapper#OK_EXIT_CODE}. + * @return DialogWrapper exit code, for example, {@link DialogWrapper#OK_EXIT_CODE}. */ int handleDialog(T dialog); + } diff --git a/plugins/git4idea/testFramework/git4idea/test/TestDialogManager.java b/plugins/git4idea/testFramework/git4idea/test/TestDialogManager.java index 0bb946986463..513c4f964cf3 100644 --- a/plugins/git4idea/testFramework/git4idea/test/TestDialogManager.java +++ b/plugins/git4idea/testFramework/git4idea/test/TestDialogManager.java @@ -16,27 +16,16 @@ package git4idea.test; import com.intellij.openapi.ui.DialogWrapper; -import org.jetbrains.annotations.Nullable; +import com.intellij.util.containers.ContainerUtil; +import git4idea.DialogManager; +import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Field; -import java.util.HashMap; import java.util.Map; /** - * TestDialogManager instead of showing the dialog, gives the control to a {@link TestDialogHandler} which can specify the dialog exit code - * (thus simulation different user choices) or even change other elements in the dialog. - * To use it a test should: - *
    - *
  1. register {@link TestDialogManager} as the {@link DialogManager} implementation: - *
    
    - *     String key = "git4idea.DialogManager";
    - *     MutablePicoContainer picoContainer = (MutablePicoContainer) myProject.getPicoContainer();
    - *     picoContainer.unregisterComponent(key);
    - *     picoContainer.registerComponentImplementation(key, TestDialogManager.class);
    - *     TestDialogManager dialogManager = (TestDialogManager)DialogManager.getInstance(myProject);
    - * 
  2. - * - *
  3. register its {@link TestDialogHandler}: + *

    TestDialogManager instead of showing the dialog, gives the control to a {@link git4idea.test.TestDialogHandler} which can specify the dialog exit code + * (thus simulation different user choices) or even change other elements in the dialog.

    + *

    To use it a test should register the {@link TestDialogHandler} implementation. For example: *

    
      *     myDialogManager.registerDialogHandler(GitConvertFilesDialog.class, new TestDialogHandler() {
      *       @Override public int handleDialog(GitConvertFilesDialog dialog) {
    @@ -44,48 +33,24 @@ import java.util.Map;
      *         return GitConvertFilesDialog.OK_EXIT_CODE;
      *       }
      *     });
    - * 
+ * + *

Only one TestDialogHandler can be registered per test for a certain DialogWrapper class.

* @see TestDialogHandler * @author Kirill Likhodedov */ -public class TestDialogManager { +public class TestDialogManager extends DialogManager { - private Map myHandlers = new HashMap(); + private final Map myHandlers = ContainerUtil.newHashMap(); - private DialogWrapper myLastShownDialog; - - public void show(DialogWrapper dialog) throws IllegalAccessException, NoSuchFieldException { - final TestDialogHandler handler = myHandlers.get(dialog.getClass()); - int exitCode = DialogWrapper.OK_EXIT_CODE; - if (handler != null) { - exitCode = handler.handleDialog(dialog); - } - closeDialog(dialog, exitCode); - myLastShownDialog = dialog; + @Override + protected void showDialog(@NotNull DialogWrapper dialog) { + TestDialogHandler handler = myHandlers.get(dialog.getClass()); + int exitCode = handler != null ? handler.handleDialog(dialog) : DialogWrapper.OK_EXIT_CODE; + dialog.close(exitCode, exitCode == DialogWrapper.OK_EXIT_CODE); } - private static void closeDialog(DialogWrapper dialog, int exitCode) throws NoSuchFieldException, IllegalAccessException { - Field exitCodeField = DialogWrapper.class.getDeclaredField("myExitCode"); - exitCodeField.setAccessible(true); - exitCodeField.set(dialog, exitCode); - - Field closedField = DialogWrapper.class.getDeclaredField("myClosed"); - closedField.setAccessible(true); - closedField.set(dialog, true); - } - - /** - * Registers the dialog handler. Note that a test may register only one handler for one dialog type. - * For different dialog types it may register different handlers. - * @param dialogClass class of the dialog which will be handled instead of showing (dialog itself have to support this in its show() method). - * @param handler handler which will be invoked when dialog is about to show. - */ - public void registerDialogHandler(Class dialogClass, TestDialogHandler handler) { + public void registerDialogHandler(@NotNull Class dialogClass, @NotNull TestDialogHandler handler) { myHandlers.put(dialogClass, handler); } - @Nullable - public DialogWrapper getLastShownDialog() { - return myLastShownDialog; - } } diff --git a/plugins/git4idea/testFramework/git4idea/test/TestNotificator.java b/plugins/git4idea/testFramework/git4idea/test/TestNotificator.java index 143d6f0d93c7..5ec1af3ae81c 100644 --- a/plugins/git4idea/testFramework/git4idea/test/TestNotificator.java +++ b/plugins/git4idea/testFramework/git4idea/test/TestNotificator.java @@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable; */ public class TestNotificator extends Notificator { - private static final String TEST_NOTIFICATION_GROUP = "Test"; + public static final String TEST_NOTIFICATION_GROUP = "Test"; private Notification myLastNotification; public TestNotificator(@NotNull Project project) { @@ -40,6 +40,10 @@ public class TestNotificator extends Notificator { return myLastNotification; } + public void notify(@NotNull Notification notification) { + myLastNotification = notification; + } + public void notify(@NotNull NotificationGroup notificationGroup, @NotNull String title, @NotNull String message, @NotNull NotificationType type) { notify(notificationGroup, title, message, type, null); } diff --git a/plugins/github/github-test/github-test.iml b/plugins/github/github-test/github-test.iml new file mode 100644 index 000000000000..2924f3eb6f79 --- /dev/null +++ b/plugins/github/github-test/github-test.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/plugins/github/github-test/test/org/jetbrains/plugins/github/GithubShareTest.java b/plugins/github/github-test/test/org/jetbrains/plugins/github/GithubShareTest.java new file mode 100644 index 000000000000..14a52112f308 --- /dev/null +++ b/plugins/github/github-test/test/org/jetbrains/plugins/github/GithubShareTest.java @@ -0,0 +1,57 @@ +package org.jetbrains.plugins.github; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.testFramework.TestActionEvent; +import git4idea.test.TestDialogHandler; +import git4idea.test.TestNotificator; +import org.jetbrains.plugins.github.test.GithubTest; +import org.jetbrains.plugins.github.ui.GithubShareDialog; + +import static git4idea.test.GitTestUtil.assertNotification; + +/** + * @author Kirill Likhodedov + */ +public class GithubShareTest extends GithubTest { + + private static final String PROJECT_NAME = "new_project_from_test"; + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + removeCreatedProject(); + super.tearDown(); + } + + private void removeCreatedProject() { + // TODO: send a request to GitHub to remove the created project + } + + public void testSimpleCase() throws Throwable { + myDialogManager.registerDialogHandler(GithubShareDialog.class, new TestDialogHandler() { + @Override + public int handleDialog(GithubShareDialog dialog) { + dialog.setRepositoryName(PROJECT_NAME); + return DialogWrapper.OK_EXIT_CODE; + } + }); + + // TODO: refactor the production code to avoid programmatically invoking the action with a fake data context + AnAction shareAction = ActionManager.getInstance().getAction("Github.Share"); + shareAction.actionPerformed(new TestActionEvent(shareAction)); + + assertNotification(myProject, new Notification(TestNotificator.TEST_NOTIFICATION_GROUP, "Success", + "Successfully created project '" + PROJECT_NAME + "' on GitHub", + NotificationType.INFORMATION)); + // TODO check that the project was created on github, and the repository was created locally and pushed there + } + +} diff --git a/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java b/plugins/github/github-test/test/org/jetbrains/plugins/github/test/GithubTest.java similarity index 51% rename from plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java rename to plugins/github/github-test/test/org/jetbrains/plugins/github/test/GithubTest.java index f64d9e466739..1c90ea791b25 100644 --- a/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java +++ b/plugins/github/github-test/test/org/jetbrains/plugins/github/test/GithubTest.java @@ -15,40 +15,56 @@ */ package org.jetbrains.plugins.github.test; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.util.PlatformUtilsCore; +import git4idea.DialogManager; +import git4idea.Notificator; import git4idea.config.GitVcsSettings; import git4idea.test.GitExecutor; +import git4idea.test.TestDialogManager; +import git4idea.test.TestNotificator; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.github.GithubSettings; -import static com.intellij.dvcs.test.Executor.cd; -import static git4idea.test.GitExecutor.git; +import static org.junit.Assume.assumeNotNull; /** - * The base class for JUnit platform tests of the github plugin.
- * Extend this test to write a test on GitHub which has the following features/limitations: + *

The base class for JUnit platform tests of the github plugin.
+ * Extend this test to write a test on GitHub which has the following features/limitations: *

    *
  • This is a "platform test case", which means that IDEA [almost] production platform is set up before the test starts.
  • *
  • Project base directory is the root of everything.
  • - *
+ *

+ *

All tests inherited from this class are required to have a login and a password to access the Github server. + * They are set up in System properties:
+ * -Dtest.github.login=mylogin
+ * -Dtest.github.password=mypassword
+ *

* * @author Kirill Likhodedov */ public abstract class GithubTest extends UsefulTestCase { + @NotNull protected static final String HOST = "github.com"; + @NotNull protected Project myProject; @NotNull protected VirtualFile myProjectRoot; - @NotNull protected GitVcsSettings mySettings; + @NotNull protected GitVcsSettings myGitSettings; + @NotNull protected GithubSettings myGitHubSettings; + + @NotNull protected TestDialogManager myDialogManager; + @NotNull protected TestNotificator myNotificator; @NotNull private IdeaProjectTestFixture myProjectFixture; @SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors", "UnusedDeclaration"}) protected GithubTest() { - PlatformTestCase.initPlatformLangPrefix(); + System.setProperty(PlatformUtilsCore.PLATFORM_PREFIX_KEY, "PlatformLangXml"); } @Override @@ -60,11 +76,23 @@ public abstract class GithubTest extends UsefulTestCase { myProject = myProjectFixture.getProject(); myProjectRoot = myProject.getBaseDir(); - cd(myProjectRoot); - git("version"); + String login = System.getProperty("test.github.login"); + String password = System.getProperty("test.github.password"); - mySettings = GitVcsSettings.getInstance(myProject); - mySettings.getAppSettings().setPathToGit(GitExecutor.GIT_EXECUTABLE); + // TODO change to assert when a stable Github testing server is ready + assumeNotNull(login); + assumeNotNull(password); + + myGitSettings = GitVcsSettings.getInstance(myProject); + myGitSettings.getAppSettings().setPathToGit(GitExecutor.GIT_EXECUTABLE); + + myGitHubSettings = GithubSettings.getInstance(); + myGitHubSettings.setHost(HOST); + myGitHubSettings.setLogin(login); + myGitHubSettings.setPassword(password); + + myDialogManager = (TestDialogManager)ServiceManager.getService(DialogManager.class); + myNotificator = (TestNotificator)ServiceManager.getService(myProject, Notificator.class); } @Override diff --git a/plugins/github/github.iml b/plugins/github/github.iml index ea922cfeadc6..2ed64b7ebcd6 100644 --- a/plugins/github/github.iml +++ b/plugins/github/github.iml @@ -5,7 +5,7 @@ - + diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java index 12c193802a92..cfbcc6367fb3 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java @@ -28,13 +28,13 @@ import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsShowConfirmationOption; import com.intellij.openapi.vcs.changes.ui.SelectFilesDialog; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ThrowableConsumer; import com.intellij.util.containers.HashSet; +import git4idea.DialogManager; import git4idea.GitLocalBranch; import git4idea.GitUtil; import git4idea.GitVcs; @@ -162,7 +162,8 @@ public class GithubShareAction extends DumbAwareAction { // Show dialog (window) final GithubShareDialog shareDialog = new GithubShareDialog(project, repoNamesRef.get(), userInfoRef.get().getMaxPrivateRepos() > userInfoRef.get().getPrivateRepos()); - shareDialog.show(); + //shareDialog.show(); + DialogManager.show(shareDialog); if (!shareDialog.isOK()) { return; } @@ -342,7 +343,7 @@ public class GithubShareAction extends DumbAwareAction { ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { - dialog.show(); + DialogManager.show(dialog); } }, indicator.getModalityState()); final Collection files2add = dialog.getSelectedFiles(); diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubShareDialog.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubShareDialog.java index 1ace8a699d98..974fb4192980 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubShareDialog.java +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubShareDialog.java @@ -4,6 +4,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.util.Set; @@ -83,4 +84,9 @@ public class GithubShareDialog extends DialogWrapper { public String getDescription() { return myGithubSharePanel.getDescription(); } + + @TestOnly + public void setRepositoryName(@NotNull String name) { + myGithubSharePanel.setRepositoryName(name); + } }