diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index f726e09103..e359cd6022 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -138,6 +138,7 @@ + diff --git a/src/GitHub.App/Services/TeamExplorerContext.cs b/src/GitHub.App/Services/TeamExplorerContext.cs new file mode 100644 index 0000000000..c8aab93df0 --- /dev/null +++ b/src/GitHub.App/Services/TeamExplorerContext.cs @@ -0,0 +1,35 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.Composition; +using GitHub.Models; + +namespace GitHub.Services +{ + [Export(typeof(ITeamExplorerContext))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class TeamExplorerContext : ITeamExplorerContext + { + readonly ITeamExplorerServiceHolder teamExplorerServiceHolder; + + [ImportingConstructor] + public TeamExplorerContext(ITeamExplorerServiceHolder teamExplorerServiceHolder) + { + this.teamExplorerServiceHolder = teamExplorerServiceHolder; + + teamExplorerServiceHolder.Subscribe(this, repo => + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ActiveRepository))); + }); + + teamExplorerServiceHolder.StatusChanged += (s, e) => + { + StatusChanged?.Invoke(this, e); + }; + } + + public ILocalRepositoryModel ActiveRepository => teamExplorerServiceHolder.ActiveRepo; + + public event EventHandler StatusChanged; + public event PropertyChangedEventHandler PropertyChanged; + } +} diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index dc0c9d224c..4bf3018444 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -154,6 +154,7 @@ + diff --git a/src/GitHub.Exports/Services/ITeamExplorerContext.cs b/src/GitHub.Exports/Services/ITeamExplorerContext.cs new file mode 100644 index 0000000000..7429997e81 --- /dev/null +++ b/src/GitHub.Exports/Services/ITeamExplorerContext.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface ITeamExplorerContext : INotifyPropertyChanged + { + ILocalRepositoryModel ActiveRepository { get; } + event EventHandler StatusChanged; + } +} diff --git a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs index ae60d6c8de..515d8af252 100644 --- a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs +++ b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs @@ -47,6 +47,11 @@ public interface ITeamExplorerServiceHolder /// Refresh the information on the active repo (in case of remote url changes or other such things) /// void Refresh(); + + /// + /// Fired when the repo or its status changes. + /// + event EventHandler StatusChanged; } public interface IGitAwareItem diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs index 106c67d6cf..04ae698577 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs @@ -53,19 +53,24 @@ public PullRequestSessionManager( IPullRequestSessionService sessionService, IConnectionManager connectionManager, IModelServiceFactory modelServiceFactory, - ITeamExplorerServiceHolder teamExplorerService) + ITeamExplorerContext teamExplorerContext) { Guard.ArgumentNotNull(service, nameof(service)); Guard.ArgumentNotNull(sessionService, nameof(sessionService)); Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); - Guard.ArgumentNotNull(teamExplorerService, nameof(teamExplorerService)); + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); this.service = service; this.sessionService = sessionService; this.connectionManager = connectionManager; this.modelServiceFactory = modelServiceFactory; - teamExplorerService.Subscribe(this, x => RepoChanged(x).Forget()); + + RepoChanged(teamExplorerContext.ActiveRepository).Forget(); + teamExplorerContext.StatusChanged += (s, e) => + { + RepoChanged(teamExplorerContext.ActiveRepository).Forget(); + }; } /// diff --git a/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs b/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs index eb0912372c..1e9976f69f 100644 --- a/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs +++ b/src/GitHub.TeamFoundation.14/Base/TeamExplorerServiceHolder.cs @@ -197,9 +197,14 @@ async void UIContextChanged(bool active, bool refresh) void UpdateActiveRepo() { var repo = gitService.ActiveRepositories.FirstOrDefault(); + if (!Equals(repo, ActiveRepo)) + { // so annoying that this is on the wrong thread syncContext.Post(r => ActiveRepo = r as ILocalRepositoryModel, repo); + } + + StatusChanged?.Invoke(this, EventArgs.Empty); } void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -269,6 +274,8 @@ IVSUIContextFactory UIContextFactory return uiContextFactory; } } + + public event EventHandler StatusChanged; } [Export(typeof(IVSUIContextFactory))] diff --git a/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj index 1cc2c42efb..471f99879e 100644 --- a/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj +++ b/test/GitHub.InlineReviews.UnitTests/GitHub.InlineReviews.UnitTests.csproj @@ -143,7 +143,6 @@ - diff --git a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs index 2a38ff7806..e31e4acae5 100644 --- a/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs +++ b/test/GitHub.InlineReviews.UnitTests/Services/PullRequestSessionManagerTests.cs @@ -5,7 +5,6 @@ using System.Reactive.Subjects; using System.Text; using System.Threading.Tasks; -using GitHub.Extensions; using GitHub.Factories; using GitHub.InlineReviews.Models; using GitHub.InlineReviews.Services; @@ -50,11 +49,30 @@ public void ReadsPullRequestFromCorrectFork() Substitute.For(), connectionManager, modelFactory, - new FakeTeamExplorerServiceHolder(repositoryModel)); + CreateTeamExplorerContext(repositoryModel)); var modelService = modelFactory.CreateBlocking(connectionManager.Connections[0]); modelService.Received(1).GetPullRequest("fork", "repo", 15); } + + [Fact] + public void LocalRepositoryModelNull() + { + var repositoryModel = null as LocalRepositoryModel; + var service = CreatePullRequestService(); + var connectionManager = CreateConnectionManager(); + var modelFactory = CreateModelServiceFactory(); + var teamExplorerContext = CreateTeamExplorerContext(repositoryModel); + + var target = new PullRequestSessionManager( + service, + Substitute.For(), + connectionManager, + modelFactory, + teamExplorerContext); + + Assert.Null(target.CurrentSession); + } } public class TheCurrentSessionProperty : PullRequestSessionManagerTests @@ -67,7 +85,7 @@ public void CreatesSessionForCurrentBranch() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); Assert.NotNull(target.CurrentSession); Assert.True(target.CurrentSession.IsCheckedOut); @@ -84,7 +102,7 @@ public void CurrentSessionIsNullIfNoPullRequestForCurrentBranch() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); Assert.Null(target.CurrentSession); } @@ -93,18 +111,18 @@ public void CurrentSessionIsNullIfNoPullRequestForCurrentBranch() public void CurrentSessionChangesWhenBranchChanges() { var service = CreatePullRequestService(); - var teService = new FakeTeamExplorerServiceHolder(CreateRepositoryModel()); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); var target = new PullRequestSessionManager( service, Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - teService); + teamExplorerContext); var session = target.CurrentSession; service.GetPullRequestForCurrentBranch(null).ReturnsForAnyArgs(Observable.Return(Tuple.Create("foo", 22))); - teService.NotifyActiveRepoChanged(); + teamExplorerContext.StatusChanged += Raise.Event(); Assert.NotSame(session, target.CurrentSession); } @@ -112,17 +130,17 @@ public void CurrentSessionChangesWhenBranchChanges() [Fact] public void CurrentSessionChangesWhenRepoChanged() { - var teService = new FakeTeamExplorerServiceHolder(CreateRepositoryModel()); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); var target = new PullRequestSessionManager( CreatePullRequestService(), Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - teService); + teamExplorerContext); var session = target.CurrentSession; - teService.ActiveRepo = CreateRepositoryModel("https://github.com/owner/other"); + SetActiveRepository(teamExplorerContext, CreateRepositoryModel("https://github.com/owner/other")); Assert.NotSame(session, target.CurrentSession); } @@ -130,17 +148,17 @@ public void CurrentSessionChangesWhenRepoChanged() [Fact] public void RepoChangedDoesntCreateNewSessionIfNotNecessary() { - var teService = new FakeTeamExplorerServiceHolder(CreateRepositoryModel()); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); var target = new PullRequestSessionManager( CreatePullRequestService(), Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - teService); + teamExplorerContext); var session = target.CurrentSession; - teService.NotifyActiveRepoChanged(); + teamExplorerContext.StatusChanged += Raise.Event(); Assert.Same(session, target.CurrentSession); } @@ -148,15 +166,15 @@ public void RepoChangedDoesntCreateNewSessionIfNotNecessary() [Fact] public void RepoChangedHandlesNullRepository() { - var teService = new FakeTeamExplorerServiceHolder(CreateRepositoryModel()); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); var target = new PullRequestSessionManager( CreatePullRequestService(), Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - teService); + teamExplorerContext); - teService.ActiveRepo = null; + SetActiveRepository(teamExplorerContext, null); Assert.Null(target.CurrentSession); } @@ -169,7 +187,7 @@ public void CreatesSessionWithCorrectRepositoryOwner() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); Assert.Equal("this-owner", target.CurrentSession.RepositoryOwner); } @@ -189,7 +207,7 @@ public async Task BaseShaIsSet() CreateSessionService(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Same("BASESHA", file.BaseSha); @@ -205,7 +223,7 @@ public async Task CommitShaIsSet() CreateSessionService(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Same("TIPSHA", file.CommitSha); @@ -221,7 +239,7 @@ public async Task CommitShaIsNullIfModified() CreateSessionService(true), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Null(file.CommitSha); @@ -249,7 +267,7 @@ public async Task DiffIsSet() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Same(diff, file.Diff); @@ -267,7 +285,7 @@ public async Task InlineCommentThreadsIsSet() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); sessionService.BuildCommentThreads( target.CurrentSession.PullRequest, @@ -296,7 +314,7 @@ public async Task CreatesTrackingPointsForThreads() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); sessionService.BuildCommentThreads( target.CurrentSession.PullRequest, @@ -315,14 +333,14 @@ public async Task MovingToNoRepositoryShouldNullOutProperties() var textView = CreateTextView(); var sessionService = CreateSessionService(); var threads = new List(); - var teHolder = new FakeTeamExplorerServiceHolder(CreateRepositoryModel()); + var teamExplorerContext = CreateTeamExplorerContext(CreateRepositoryModel()); var target = new PullRequestSessionManager( CreatePullRequestService(), CreateSessionService(), CreateConnectionManager(), CreateModelServiceFactory(), - teHolder); + teamExplorerContext); sessionService.BuildCommentThreads( target.CurrentSession.PullRequest, @@ -338,7 +356,7 @@ public async Task MovingToNoRepositoryShouldNullOutProperties() Assert.NotNull(file.InlineCommentThreads); Assert.NotNull(file.TrackingPoints); - teHolder.ActiveRepo = null; + SetActiveRepository(teamExplorerContext, null); Assert.Null(file.BaseSha); Assert.Null(file.CommitSha); @@ -366,7 +384,7 @@ public async Task ModifyingBufferMarksThreadsAsStaleAndSignalsRebuild() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); sessionService.BuildCommentThreads( target.CurrentSession.PullRequest, @@ -409,7 +427,7 @@ public async Task RebuildSignalUpdatesCommitSha() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Same("TIPSHA", file.CommitSha); @@ -430,7 +448,7 @@ public async Task ClosingTextViewDisposesFile() CreateSessionService(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); var compositeDisposable = file.ToDispose as CompositeDisposable; @@ -474,7 +492,7 @@ Line 2 CreateRealSessionService(diff: diffService), CreateConnectionManager(), CreateModelServiceFactory(pullRequest), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Equal(1, file.InlineCommentThreads.Count); @@ -520,7 +538,7 @@ Line 2 CreateRealSessionService(diff: diffService), CreateConnectionManager(), CreateModelServiceFactory(pullRequest), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Equal(1, file.InlineCommentThreads.Count); @@ -580,7 +598,7 @@ Line 2 CreateRealSessionService(diff: diffService), CreateConnectionManager(), CreateModelServiceFactory(pullRequest), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Equal("Original Comment", file.InlineCommentThreads[0].Comments[0].Body); @@ -635,7 +653,7 @@ Line 2 CreateRealSessionService(diff: diffService), CreateConnectionManager(), CreateModelServiceFactory(pullRequest), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = (PullRequestSessionLiveFile)await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Equal(1, file.InlineCommentThreads[0].Comments.Count); @@ -666,7 +684,7 @@ public async Task CommitShaIsUpdatedOnTextChange() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); Assert.Equal("TIPSHA", file.CommitSha); @@ -695,7 +713,7 @@ public async Task UpdatingCurrentSessionPullRequestTriggersLinesChanged() sessionService, CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var file = await target.GetLiveFile(FilePath, textView, textView.TextBuffer); var raised = false; var pullRequest = target.CurrentSession.PullRequest; @@ -807,7 +825,7 @@ public async Task GetSessionReturnsAndUpdatesCurrentSessionIfNumbersMatch() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var newModel = CreatePullRequestModel(CurrentBranchPullRequestNumber); var result = await target.GetSession(newModel); @@ -824,7 +842,7 @@ public async Task GetSessionReturnsNewSessionForPullRequestWithDifferentNumber() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var newModel = CreatePullRequestModel(NotCurrentBranchPullRequestNumber); var result = await target.GetSession(newModel); @@ -842,7 +860,7 @@ public async Task GetSessionReturnsNewSessionForPullRequestWithDifferentBaseOwne Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var newModel = CreatePullRequestModel(CurrentBranchPullRequestNumber, "https://github.com/fork/repo"); var result = await target.GetSession(newModel); @@ -860,7 +878,7 @@ public async Task GetSessionReturnsSameSessionEachTime() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); var newModel = CreatePullRequestModel(NotCurrentBranchPullRequestNumber); var result1 = await target.GetSession(newModel); @@ -879,7 +897,7 @@ public async Task SessionCanBeCollected() Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); Func run = async () => { @@ -912,7 +930,7 @@ public async Task GetSessionUpdatesCurrentSessionIfCurrentBranchIsPullRequestBut Substitute.For(), CreateConnectionManager(), CreateModelServiceFactory(), - new FakeTeamExplorerServiceHolder(CreateRepositoryModel())); + CreateTeamExplorerContext(CreateRepositoryModel())); Assert.Null(target.CurrentSession); @@ -983,5 +1001,18 @@ ILocalRepositoryModel CreateRepositoryModel(string cloneUrl = OwnerCloneUrl) result.Owner.Returns(uriString.Owner); return result; } + + static ITeamExplorerContext CreateTeamExplorerContext(ILocalRepositoryModel repo) + { + var teamExplorerContext = Substitute.For(); + teamExplorerContext.ActiveRepository.Returns(repo); + return teamExplorerContext; + } + + static void SetActiveRepository(ITeamExplorerContext teamExplorerContext, ILocalRepositoryModel localRepositoryModel) + { + teamExplorerContext.ActiveRepository.Returns(localRepositoryModel); + teamExplorerContext.StatusChanged += Raise.Event(); + } } } diff --git a/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeTeamExplorerServiceHolder.cs b/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeTeamExplorerServiceHolder.cs deleted file mode 100644 index e6f5d1b2a9..0000000000 --- a/test/GitHub.InlineReviews.UnitTests/TestDoubles/FakeTeamExplorerServiceHolder.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using GitHub.Models; -using GitHub.Services; - -namespace GitHub.InlineReviews.UnitTests.TestDoubles -{ - class FakeTeamExplorerServiceHolder : ITeamExplorerServiceHolder - { - ILocalRepositoryModel activeRepo; - Action listener; - - public FakeTeamExplorerServiceHolder() - { - } - - public FakeTeamExplorerServiceHolder(ILocalRepositoryModel repo) - { - ActiveRepo = repo; - } - - public ILocalRepositoryModel ActiveRepo - { - get { return activeRepo; } - set { activeRepo = value; NotifyActiveRepoChanged(); } - } - - public IGitAwareItem HomeSection { get; set; } - - public IServiceProvider ServiceProvider { get; set; } - - public void ClearServiceProvider(IServiceProvider provider) - { - } - - public void NotifyActiveRepoChanged() - { - listener?.Invoke(activeRepo); - } - - public void Refresh() - { - } - - public void Subscribe(object who, Action handler) - { - listener = handler; - handler(ActiveRepo); - } - - public void Unsubscribe(object who) - { - } - } -}