Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions GitHubExtension.Test/Controls/CreateFormsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text.Json;
using GitHubExtension.Controls;
using GitHubExtension.Controls.Forms;
using GitHubExtension.DataManager;
using GitHubExtension.Helpers;
using Moq;

namespace GitHubExtension.Test.Controls;

[TestClass]
public class CreateFormsTests
{
private static Mock<IResources> CreateResources()
{
var resources = new Mock<IResources>();
resources.Setup(x => x.GetResource(It.IsAny<string>(), null)).Returns<string, object>((key, _) => key);
return resources;
}

[TestMethod]
[TestCategory("Unit")]
public void CreateIssueForm_TemplateJson_IsValidJson()
{
var createManager = new Mock<IGitHubCreateManager>();
var form = new CreateIssueForm(createManager.Object, CreateResources().Object, "owner/repo");

var json = form.TemplateJson;

var parsed = JsonDocument.Parse(json);
Assert.AreEqual("AdaptiveCard", parsed.RootElement.GetProperty("type").GetString());
}

[TestMethod]
[TestCategory("Unit")]
public void CreatePullRequestForm_TemplateJson_IsValidJson()
{
var createManager = new Mock<IGitHubCreateManager>();
var form = new CreatePullRequestForm(createManager.Object, CreateResources().Object);

var json = form.TemplateJson;

var parsed = JsonDocument.Parse(json);
Assert.AreEqual("AdaptiveCard", parsed.RootElement.GetProperty("type").GetString());
}

[TestMethod]
[TestCategory("Unit")]
public void CreateBranchForm_TemplateJson_IsValidJson()
{
var createManager = new Mock<IGitHubCreateManager>();
var form = new CreateBranchForm(createManager.Object, CreateResources().Object);

var json = form.TemplateJson;

var parsed = JsonDocument.Parse(json);
Assert.AreEqual("AdaptiveCard", parsed.RootElement.GetProperty("type").GetString());
}

[TestMethod]
[TestCategory("Unit")]
public void AddCommentForm_TemplateJson_IsValidJson()
{
var createManager = new Mock<IGitHubCreateManager>();
var issue = new Mock<IIssue>();
issue.Setup(x => x.Title).Returns("An issue title");
var form = new AddCommentForm(issue.Object, createManager.Object, CreateResources().Object);

var json = form.TemplateJson;

var parsed = JsonDocument.Parse(json);
Assert.AreEqual("AdaptiveCard", parsed.RootElement.GetProperty("type").GetString());
}
}
57 changes: 57 additions & 0 deletions GitHubExtension.Test/Controls/GitHubCreateManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using GitHubExtension.DataManager.Data;

namespace GitHubExtension.Test.Controls;

[TestClass]
public class GitHubCreateManagerTests
{
[TestMethod]
[TestCategory("Unit")]
public void ParseOwnerRepo_ShortForm_ParsesOwnerAndRepo()
{
var (owner, repo) = GitHubCreateManager.ParseOwnerRepo("octocat/hello-world");

Assert.AreEqual("octocat", owner);
Assert.AreEqual("hello-world", repo);
}

[TestMethod]
[TestCategory("Unit")]
public void ParseOwnerRepo_WithSurroundingWhitespace_Trims()
{
var (owner, repo) = GitHubCreateManager.ParseOwnerRepo(" octocat / hello-world ");

Assert.AreEqual("octocat", owner);
Assert.AreEqual("hello-world", repo);
}

[TestMethod]
[TestCategory("Unit")]
public void ParseOwnerRepo_FullUrl_ParsesOwnerAndRepo()
{
var (owner, repo) = GitHubCreateManager.ParseOwnerRepo("https://github.com/octocat/hello-world");

Assert.AreEqual("octocat", owner);
Assert.AreEqual("hello-world", repo);
}

[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentException))]
public void ParseOwnerRepo_Empty_Throws()
{
GitHubCreateManager.ParseOwnerRepo(" ");
}

[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentException))]
public void ParseOwnerRepo_MissingRepo_Throws()
{
GitHubCreateManager.ParseOwnerRepo("octocat");
}
}
11 changes: 6 additions & 5 deletions GitHubExtension.Test/Controls/MutationCommandsFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ public class MutationCommandsFactoryTests
private static (MutationCommandsFactory Factory, Mock<IGitHubMutationManager> Manager, MutationMediator Mediator) CreateFactory()
{
var manager = new Mock<IGitHubMutationManager>();
var createManager = new Mock<IGitHubCreateManager>();
var mediator = new MutationMediator();
var resources = new Mock<IResources>();
resources.Setup(x => x.GetResource(It.IsAny<string>(), null)).Returns<string, object>((key, _) => key);
var factory = new MutationCommandsFactory(manager.Object, mediator, resources.Object);
var factory = new MutationCommandsFactory(manager.Object, createManager.Object, mediator, resources.Object);
return (factory, manager, mediator);
}

Expand Down Expand Up @@ -51,7 +52,7 @@ public void GetIssueCommands_OpenIssue_ReturnsCloseCommand()
var (factory, _, _) = CreateFactory();
var commands = factory.GetIssueCommands(CreateIssue("Open").Object).ToList();

Assert.AreEqual(1, commands.Count);
Assert.AreEqual(2, commands.Count);
Assert.AreEqual("Commands_CloseIssue", commands[0].Command!.Name);
Assert.IsTrue(commands[0].IsCritical);
}
Expand All @@ -63,7 +64,7 @@ public void GetIssueCommands_ClosedIssue_ReturnsReopenCommand()
var (factory, _, _) = CreateFactory();
var commands = factory.GetIssueCommands(CreateIssue("Closed").Object).ToList();

Assert.AreEqual(1, commands.Count);
Assert.AreEqual(2, commands.Count);
Assert.AreEqual("Commands_ReopenIssue", commands[0].Command!.Name);
Assert.IsFalse(commands[0].IsCritical);
}
Expand All @@ -75,7 +76,7 @@ public void GetPullRequestCommands_OpenPullRequest_ReturnsMergeAndCloseCommands(
var (factory, _, _) = CreateFactory();
var commands = factory.GetPullRequestCommands(CreatePullRequest("Open").Object).ToList();

Assert.AreEqual(2, commands.Count);
Assert.AreEqual(3, commands.Count);
Assert.AreEqual("Commands_MergePullRequest", commands[0].Command!.Name);
Assert.AreEqual("Commands_ClosePullRequest", commands[1].Command!.Name);
Assert.IsTrue(commands[0].IsCritical);
Expand All @@ -89,7 +90,7 @@ public void GetPullRequestCommands_ClosedPullRequest_ReturnsReopenCommand()
var (factory, _, _) = CreateFactory();
var commands = factory.GetPullRequestCommands(CreatePullRequest("Closed").Object).ToList();

Assert.AreEqual(1, commands.Count);
Assert.AreEqual(2, commands.Count);
Assert.AreEqual("Commands_ReopenPullRequest", commands[0].Command!.Name);
Assert.IsFalse(commands[0].IsCritical);
}
Expand Down
3 changes: 2 additions & 1 deletion GitHubExtension.Test/Controls/SearchPagesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public class SearchPagesTests
private static (MutationCommandsFactory Factory, MutationMediator Mediator) CreateMutationDeps(Mock<IResources> resources)
{
var mutationManager = new Mock<IGitHubMutationManager>();
var createManager = new Mock<IGitHubCreateManager>();
var mediator = new MutationMediator();
var factory = new MutationCommandsFactory(mutationManager.Object, mediator, resources.Object);
var factory = new MutationCommandsFactory(mutationManager.Object, createManager.Object, mediator, resources.Object);
return (factory, mediator);
}

Expand Down
3 changes: 2 additions & 1 deletion GitHubExtension.Test/Controls/TopLevelSearchesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ public async Task Integration_AddNewTopLevelCommand(string searchString, string
var mockDeveloperIdProvider = TestHelpers.CreateMockDeveloperIdProvider();
var mockCacheDataManager = new Mock<ICacheDataManager>().Object;
var mutationManager = new Mock<IGitHubMutationManager>().Object;
var createManager = new Mock<IGitHubCreateManager>().Object;
var mutationMediator = new MutationMediator();
var mutationCommandsFactory = new MutationCommandsFactory(mutationManager, mutationMediator, resources);
var mutationCommandsFactory = new MutationCommandsFactory(mutationManager, createManager, mutationMediator, resources);
var searchPageFactory = new SearchPageFactory(mockCacheDataManager, persistentDataManager, resources, mediator, mutationCommandsFactory, mutationMediator);

var addSearchForm = new SaveSearchForm(persistentDataManager, resources, mediator);
Expand Down
3 changes: 2 additions & 1 deletion GitHubExtension.Test/Helpers/TestSetupHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public static GitHubExtensionCommandsProvider CreateGitHubExtensionCommandsProvi
var mockNotificationsDataManager = new Mock<INotificationsDataManager>();
mockNotificationsDataManager.Setup(x => x.GetUnreadCountAsync()).ReturnsAsync(0);
var notificationsPage = new NotificationsPage(mockNotificationsDataManager.Object, notificationsMediator, mockResources);
return new GitHubExtensionCommandsProvider(savedSearchesPage, signOutPage, signInPage, notificationsPage, mockDeveloperIdProvider, persistentDataManager, mockResources, searchPageFactory, savedSearchesMediator, mockAuthenticationMediator, notificationsMediator);
var mockCreateManager = new Mock<IGitHubCreateManager>().Object;
return new GitHubExtensionCommandsProvider(savedSearchesPage, signOutPage, signInPage, notificationsPage, mockDeveloperIdProvider, persistentDataManager, mockResources, searchPageFactory, savedSearchesMediator, mockAuthenticationMediator, notificationsMediator, mockCreateManager);
}
}
23 changes: 22 additions & 1 deletion GitHubExtension/Controls/Commands/MutationCommandsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using GitHubExtension.Controls.Forms;
using GitHubExtension.Controls.Pages;
using GitHubExtension.DataManager;
using GitHubExtension.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
Expand All @@ -13,22 +15,37 @@ namespace GitHubExtension.Controls.Commands;
public sealed class MutationCommandsFactory
{
private readonly IGitHubMutationManager _mutationManager;
private readonly IGitHubCreateManager _createManager;
private readonly MutationMediator _mediator;
private readonly IResources _resources;

private static readonly IconInfo CloseIcon = new("\uE711");
private static readonly IconInfo ReopenIcon = new("\uE72C");
private static readonly IconInfo MergeIcon = new("\uE8FB");

public MutationCommandsFactory(IGitHubMutationManager mutationManager, MutationMediator mediator, IResources resources)
public MutationCommandsFactory(IGitHubMutationManager mutationManager, IGitHubCreateManager createManager, MutationMediator mediator, IResources resources)
{
_mutationManager = mutationManager;
_createManager = createManager;
_mediator = mediator;
_resources = resources;
}

private static bool IsOpen(IIssue issue) => string.Equals(issue.State, "Open", StringComparison.OrdinalIgnoreCase);

private CommandContextItem BuildAddCommentCommand(IIssue issue)
{
var page = new GitHubFormPage(
new AddCommentForm(issue, _createManager, _resources),
_resources,
"Forms_AddComment_Title",
"\uE90A",
"Message_AddComment_Success",
"Message_AddComment_Error");

return new CommandContextItem(page);
}

public IEnumerable<CommandContextItem> GetIssueCommands(IIssue issue)
{
var items = new List<CommandContextItem>();
Expand Down Expand Up @@ -65,6 +82,8 @@ public IEnumerable<CommandContextItem> GetIssueCommands(IIssue issue)
items.Add(new CommandContextItem(reopen));
}

items.Add(BuildAddCommentCommand(issue));

return items;
}

Expand Down Expand Up @@ -121,6 +140,8 @@ public IEnumerable<CommandContextItem> GetPullRequestCommands(IPullRequest pullR
items.Add(new CommandContextItem(reopen));
}

items.Add(BuildAddCommentCommand(pullRequest));

return items;
}
}
68 changes: 68 additions & 0 deletions GitHubExtension/Controls/Forms/AddCommentForm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Text.Json;
using System.Text.Json.Nodes;
using GitHubExtension.DataManager;
using GitHubExtension.Helpers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;

namespace GitHubExtension.Controls.Forms;

public sealed partial class AddCommentForm : FormContent, IGitHubForm
{
private readonly IGitHubCreateManager _createManager;
private readonly IResources _resources;
private readonly IIssue _issue;

public event EventHandler<bool>? LoadingStateChanged;

public event EventHandler<FormSubmitEventArgs>? FormSubmitted;

public AddCommentForm(IIssue issue, IGitHubCreateManager createManager, IResources resources)
{
_issue = issue;
_createManager = createManager;
_resources = resources;
}

public Dictionary<string, string> TemplateSubstitutions => new()
{
{ "{{AddCommentFormTitle}}", JsonSerializer.Serialize(_resources.GetResource("Forms_AddComment_Title")) },
{ "{{AddCommentSubtitle}}", JsonSerializer.Serialize(_issue.Title) },
{ "{{BodyLabel}}", JsonSerializer.Serialize(_resources.GetResource("Forms_AddComment_BodyLabel")) },
{ "{{BodyPlaceholder}}", JsonSerializer.Serialize(_resources.GetResource("Forms_AddComment_BodyPlaceholder")) },
{ "{{BodyErrorMessage}}", JsonSerializer.Serialize(_resources.GetResource("Forms_AddComment_BodyError")) },
{ "{{AddCommentActionTitle}}", JsonSerializer.Serialize(_resources.GetResource("Forms_AddComment_Action")) },
};

public override string TemplateJson => TemplateHelper.LoadTemplateJsonFromTemplateName("AddComment", TemplateSubstitutions);

public override ICommandResult SubmitForm(string? inputs, string data)
{
LoadingStateChanged?.Invoke(this, true);
_ = SubmitInternalAsync(inputs);
return CommandResult.KeepOpen();
}

private async Task SubmitInternalAsync(string? inputs)
{
try
{
var payload = JsonNode.Parse(inputs ?? string.Empty) ?? throw new InvalidOperationException("No input provided.");
var body = payload["Body"]?.ToString() ?? string.Empty;

await _createManager.AddCommentAsync(_issue, body);

LoadingStateChanged?.Invoke(this, false);
FormSubmitted?.Invoke(this, new FormSubmitEventArgs(true, null));
}
catch (Exception ex)
{
LoadingStateChanged?.Invoke(this, false);
FormSubmitted?.Invoke(this, new FormSubmitEventArgs(false, ex));
}
}
}
Loading
Loading