Added TagHelper discovery to Razor project systen
This commit is contained in:
parent
90120f6a3b
commit
0a76ad7017
|
|
@ -2,6 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
|
|
@ -41,6 +44,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
ComputedVersion = other.ComputedVersion;
|
||||
Configuration = other.Configuration;
|
||||
TagHelpers = other.TagHelpers;
|
||||
}
|
||||
|
||||
private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other)
|
||||
|
|
@ -59,12 +63,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
ComputedVersion = update.UnderlyingProject.Version;
|
||||
Configuration = update.Configuration;
|
||||
TagHelpers = update.TagHelpers ?? Array.Empty<TagHelperDescriptor>();
|
||||
}
|
||||
|
||||
public override ProjectExtensibilityConfiguration Configuration { get; }
|
||||
|
||||
public override Project UnderlyingProject { get; }
|
||||
|
||||
public override IReadOnlyList<TagHelperDescriptor> TagHelpers { get; } = Array.Empty<TagHelperDescriptor>();
|
||||
|
||||
// This is the version that the computed state is based on.
|
||||
public VersionStamp? ComputedVersion { get; set; }
|
||||
|
||||
|
|
@ -92,7 +99,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
return new DefaultProjectSnapshot(update, this);
|
||||
}
|
||||
|
||||
public bool HasChangesComparedTo(ProjectSnapshot original)
|
||||
public bool HasConfigurationChanged(ProjectSnapshot original)
|
||||
{
|
||||
if (original == null)
|
||||
{
|
||||
|
|
@ -101,5 +108,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
return !object.Equals(Configuration, original.Configuration);
|
||||
}
|
||||
|
||||
public bool HaveTagHelpersChanged(ProjectSnapshot original)
|
||||
{
|
||||
if (original == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(original));
|
||||
}
|
||||
|
||||
return !Enumerable.SequenceEqual(TagHelpers, original.TagHelpers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,10 +149,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
// Now we need to know if the changes that we applied are significant. If that's the case then
|
||||
// we need to notify listeners.
|
||||
if (snapshot.HasChangesComparedTo(original))
|
||||
if (snapshot.HasConfigurationChanged(original))
|
||||
{
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
|
||||
}
|
||||
|
||||
if (snapshot.HaveTagHelpersChanged(original))
|
||||
{
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.TagHelpersChanged));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +177,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
}
|
||||
}
|
||||
|
||||
public override void ProjectBuildComplete(Project underlyingProject)
|
||||
{
|
||||
if (underlyingProject == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(underlyingProject));
|
||||
}
|
||||
|
||||
if (_projects.TryGetValue(underlyingProject.Id, out var original))
|
||||
{
|
||||
// Doing an update to the project should keep computed values, but mark the project as dirty if the
|
||||
// underlying project is newer.
|
||||
var snapshot = original.WithProjectChange(underlyingProject);
|
||||
_projects[underlyingProject.Id] = snapshot;
|
||||
|
||||
// Notify the background worker so it can trigger tag helper discovery.
|
||||
NotifyBackgroundWorker(underlyingProject);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProjectsCleared()
|
||||
{
|
||||
foreach (var kvp in _projects.ToArray())
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly TagHelperResolver _tagHelperResolver;
|
||||
|
||||
public DefaultProjectSnapshotWorker(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ProjectExtensibilityConfigurationFactory configurationFactory)
|
||||
ProjectExtensibilityConfigurationFactory configurationFactory,
|
||||
TagHelperResolver tagHelperResolver)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
|
|
@ -26,8 +28,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
throw new ArgumentNullException(nameof(configurationFactory));
|
||||
}
|
||||
|
||||
if (tagHelperResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagHelperResolver));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_configurationFactory = configurationFactory;
|
||||
_tagHelperResolver = tagHelperResolver;
|
||||
}
|
||||
|
||||
public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
|
|
@ -54,6 +62,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
var configuration = await _configurationFactory.GetConfigurationAsync(update.UnderlyingProject);
|
||||
update.Configuration = configuration;
|
||||
|
||||
var result = await _tagHelperResolver.GetTagHelpersAsync(update.UnderlyingProject);
|
||||
update.TagHelpers = result.Descriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
return new DefaultProjectSnapshotWorker(
|
||||
languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>(),
|
||||
languageServices.GetRequiredService<ProjectExtensibilityConfigurationFactory>());
|
||||
languageServices.GetRequiredService<ProjectExtensibilityConfigurationFactory>(),
|
||||
languageServices.GetRequiredService<TagHelperResolver>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
|
|
@ -10,5 +12,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
public abstract ProjectExtensibilityConfiguration Configuration { get; }
|
||||
|
||||
public abstract Project UnderlyingProject { get; }
|
||||
|
||||
public abstract IReadOnlyList<TagHelperDescriptor> TagHelpers { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,5 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
public abstract event EventHandler<ProjectChangeEventArgs> Changed;
|
||||
|
||||
public abstract IReadOnlyList<ProjectSnapshot> Projects { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
public abstract void ProjectRemoved(Project underlyingProject);
|
||||
|
||||
public abstract void ProjectBuildComplete(Project underlyingProject);
|
||||
|
||||
public abstract void ProjectsCleared();
|
||||
|
||||
public abstract void ReportError(Exception exception);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
|
|
@ -20,5 +22,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
public Project UnderlyingProject { get; }
|
||||
|
||||
public ProjectExtensibilityConfiguration Configuration { get; set; }
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> TagHelpers { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_textViews = new List<ITextView>();
|
||||
}
|
||||
|
||||
internal override ProjectExtensibilityConfiguration Configuration => _project.Configuration;
|
||||
internal override ProjectExtensibilityConfiguration Configuration => _project?.Configuration;
|
||||
|
||||
public override EditorSettings EditorSettings => _editorSettingsManager.Current;
|
||||
|
||||
public override IReadOnlyList<TagHelperDescriptor> TagHelpers => Array.Empty<TagHelperDescriptor>();
|
||||
public override IReadOnlyList<TagHelperDescriptor> TagHelpers => _project?.TagHelpers ?? Array.Empty<TagHelperDescriptor>();
|
||||
|
||||
public override bool IsSupportedProject => _isSupportedProject;
|
||||
|
||||
|
|
@ -167,7 +167,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
|
||||
// Internal for testing
|
||||
internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
|
||||
{
|
||||
if (_projectPath != null &&
|
||||
string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
private void ConfigureTemplateEngine(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new VisualStudioParserOptionsFeature(_documentTracker.EditorSettings));
|
||||
builder.Features.Add(new VisualStudioTagHelperFeature(TextBuffer));
|
||||
builder.Features.Add(new VisualStudioTagHelperFeature(_documentTracker.TagHelpers));
|
||||
}
|
||||
|
||||
private class VisualStudioParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
|
||||
|
|
@ -408,29 +408,20 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class will cease to be useful once we control TagHelper discovery. For now, it delegates discovery
|
||||
/// to ITagHelperFeature's that exist on the text buffer.
|
||||
/// </summary>
|
||||
private class VisualStudioTagHelperFeature : ITagHelperFeature
|
||||
{
|
||||
private readonly ITextBuffer _textBuffer;
|
||||
private readonly IReadOnlyList<TagHelperDescriptor> _tagHelpers;
|
||||
|
||||
public VisualStudioTagHelperFeature(ITextBuffer textBuffer)
|
||||
public VisualStudioTagHelperFeature(IReadOnlyList<TagHelperDescriptor> tagHelpers)
|
||||
{
|
||||
_textBuffer = textBuffer;
|
||||
_tagHelpers = tagHelpers;
|
||||
}
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
if (_textBuffer.Properties.TryGetProperty(typeof(ITagHelperFeature), out ITagHelperFeature feature))
|
||||
{
|
||||
return feature.GetDescriptors();
|
||||
}
|
||||
|
||||
return Array.Empty<TagHelperDescriptor>();
|
||||
return _tagHelpers;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,5 +12,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
public abstract bool IsSupportedProject(object project);
|
||||
|
||||
public abstract string GetProjectPath(object project);
|
||||
|
||||
public abstract string GetProjectName(object project);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
[ExportLanguageServiceFactory(typeof(TagHelperResolver), RazorLanguage.Name, ServiceLayer.Default)]
|
||||
internal class DefaultTagHelperResolverFactory : ILanguageServiceFactory
|
||||
{
|
||||
[Import]
|
||||
public VisualStudioWorkspace Workspace { get; set; }
|
||||
|
||||
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||
{
|
||||
return new DefaultTagHelperResolver(Workspace.Services.GetRequiredService<ErrorReporter>(), Workspace);
|
||||
var workspace = languageServices.WorkspaceServices.Workspace;
|
||||
return new DefaultTagHelperResolver(workspace.Services.GetRequiredService<ErrorReporter>(), workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -100,5 +100,21 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string GetProjectName(object project)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(project));
|
||||
}
|
||||
|
||||
var hierarchy = (IVsHierarchy)project;
|
||||
if (ErrorHandler.Failed(hierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Name, out var name)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string)name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
[Export(typeof(ProjectSnapshotChangeTrigger))]
|
||||
internal class VsSolutionUpdatesProjectSnapshotChangeTrigger : ProjectSnapshotChangeTrigger, IVsUpdateSolutionEvents2
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
|
||||
private ProjectSnapshotManagerBase _projectManager;
|
||||
|
||||
[ImportingConstructor]
|
||||
public VsSolutionUpdatesProjectSnapshotChangeTrigger(
|
||||
[Import(typeof(SVsServiceProvider))] IServiceProvider services,
|
||||
TextBufferProjectService projectService)
|
||||
{
|
||||
_services = services;
|
||||
_projectService = projectService;
|
||||
}
|
||||
|
||||
public override void Initialize(ProjectSnapshotManagerBase projectManager)
|
||||
{
|
||||
_projectManager = projectManager;
|
||||
|
||||
// Attach the event sink to solution update events.
|
||||
var solutionBuildManager = _services.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager;
|
||||
if (solutionBuildManager != null)
|
||||
{
|
||||
// We expect this to be called only once. So we don't need to Unadvise.
|
||||
var hr = solutionBuildManager.AdviseUpdateSolutionEvents(this, out var cookie);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
}
|
||||
|
||||
public int UpdateSolution_Begin(ref int pfCancelUpdate)
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int UpdateSolution_StartUpdate(ref int pfCancelUpdate)
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int UpdateSolution_Cancel()
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel)
|
||||
{
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
|
||||
public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel)
|
||||
{
|
||||
var projectName = _projectService.GetProjectName(pHierProj);
|
||||
var projectPath = _projectService.GetProjectPath(pHierProj);
|
||||
|
||||
// Get the corresponding roslyn project by matching the project name and the project path.
|
||||
foreach (var project in _projectManager.Workspace.CurrentSolution.Projects)
|
||||
{
|
||||
if (string.Equals(projectName, project.Name, StringComparison.Ordinal) &&
|
||||
string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_projectManager.ProjectBuildComplete(project);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VSConstants.S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,6 +209,38 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
Assert.False(ProjectManager.WorkerStarted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectBuildComplete_KnownProject_NotifiesBackgroundWorker()
|
||||
{
|
||||
// Arrange
|
||||
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
|
||||
ProjectManager.ProjectAdded(project);
|
||||
ProjectManager.Reset();
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectBuildComplete(project);
|
||||
|
||||
// Assert
|
||||
Assert.False(ProjectManager.ListenersNotified);
|
||||
Assert.True(ProjectManager.WorkerStarted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectBuildComplete_IgnoresUnknownProject()
|
||||
{
|
||||
// Arrange
|
||||
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectBuildComplete(project);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(ProjectManager.Projects);
|
||||
|
||||
Assert.False(ProjectManager.ListenersNotified);
|
||||
Assert.False(ProjectManager.WorkerStarted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectRemoved_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
public class DefaultProjectSnapshotTest
|
||||
{
|
||||
[Fact]
|
||||
public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesUnderlyingProject()
|
||||
{
|
||||
// Arrange
|
||||
var underlyingProject = GetProject("Test1");
|
||||
var original = new DefaultProjectSnapshot(underlyingProject);
|
||||
|
||||
var anotherProject = GetProject("Test1");
|
||||
|
||||
// Act
|
||||
var snapshot = original.WithProjectChange(anotherProject);
|
||||
|
||||
// Assert
|
||||
Assert.Same(anotherProject, snapshot.UnderlyingProject);
|
||||
Assert.Equal(original.ComputedVersion, snapshot.ComputedVersion);
|
||||
Assert.Equal(original.Configuration, snapshot.Configuration);
|
||||
Assert.Equal(original.TagHelpers, snapshot.TagHelpers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesValues()
|
||||
{
|
||||
// Arrange
|
||||
var underlyingProject = GetProject("Test1");
|
||||
var original = new DefaultProjectSnapshot(underlyingProject);
|
||||
|
||||
var anotherProject = GetProject("Test1");
|
||||
var update = new ProjectSnapshotUpdateContext(anotherProject)
|
||||
{
|
||||
Configuration = Mock.Of<ProjectExtensibilityConfiguration>(),
|
||||
TagHelpers = Array.Empty<TagHelperDescriptor>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var snapshot = original.WithProjectChange(update);
|
||||
|
||||
// Assert
|
||||
Assert.Same(original.UnderlyingProject, snapshot.UnderlyingProject);
|
||||
Assert.Equal(update.UnderlyingProject.Version, snapshot.ComputedVersion);
|
||||
Assert.Same(update.Configuration, snapshot.Configuration);
|
||||
Assert.Same(update.TagHelpers, snapshot.TagHelpers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HaveTagHelpersChanged_NoUpdatesToTagHelpers_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var underlyingProject = GetProject("Test1");
|
||||
var original = new DefaultProjectSnapshot(underlyingProject);
|
||||
|
||||
var anotherProject = GetProject("Test1");
|
||||
var update = new ProjectSnapshotUpdateContext(anotherProject);
|
||||
var snapshot = original.WithProjectChange(update);
|
||||
|
||||
// Act
|
||||
var result = snapshot.HaveTagHelpersChanged(original);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HaveTagHelpersChanged_TagHelpersUpdated_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var underlyingProject = GetProject("Test1");
|
||||
var original = new DefaultProjectSnapshot(underlyingProject);
|
||||
|
||||
var anotherProject = GetProject("Test1");
|
||||
var update = new ProjectSnapshotUpdateContext(anotherProject)
|
||||
{
|
||||
TagHelpers = new[]
|
||||
{
|
||||
TagHelperDescriptorBuilder.Create("One", "TestAssembly").Build(),
|
||||
TagHelperDescriptorBuilder.Create("Two", "TestAssembly").Build(),
|
||||
},
|
||||
};
|
||||
var snapshot = original.WithProjectChange(update);
|
||||
|
||||
// Act
|
||||
var result = snapshot.HaveTagHelpersChanged(original);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
private Project GetProject(string name)
|
||||
{
|
||||
var project = new AdhocWorkspace().AddProject(name, LanguageNames.CSharp);
|
||||
return project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var called = false;
|
||||
documentTracker.ContextChanged += (sender, args) =>
|
||||
{
|
||||
Assert.Equal(ContextChangeKind.EditorSettingsChanged, args.Kind);
|
||||
called = true;
|
||||
Assert.Equal(ContextChangeKind.EditorSettingsChanged, args.Kind);
|
||||
};
|
||||
|
|
@ -49,6 +50,77 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectManager_Changed_ProjectChanged_TriggersContextChanged()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectPath, ProjectManager, EditorSettingsManager, Workspace, TextBuffer);
|
||||
|
||||
var project = new AdhocWorkspace().AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), new VersionStamp(), "Test1", "TestAssembly", LanguageNames.CSharp, filePath: "C:/Some/Path/TestProject.csproj"));
|
||||
var projectSnapshot = new DefaultProjectSnapshot(project);
|
||||
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
|
||||
|
||||
var called = false;
|
||||
documentTracker.ContextChanged += (sender, args) =>
|
||||
{
|
||||
Assert.Equal(ContextChangeKind.ProjectChanged, args.Kind);
|
||||
called = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
documentTracker.ProjectManager_Changed(null, projectChangedArgs);
|
||||
|
||||
// Assert
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectManager_Changed_TagHelpersChanged_TriggersContextChanged()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectPath, ProjectManager, EditorSettingsManager, Workspace, TextBuffer);
|
||||
|
||||
var project = new AdhocWorkspace().AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), new VersionStamp(), "Test1", "TestAssembly", LanguageNames.CSharp, filePath: "C:/Some/Path/TestProject.csproj"));
|
||||
var projectSnapshot = new DefaultProjectSnapshot(project);
|
||||
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.TagHelpersChanged);
|
||||
|
||||
var called = false;
|
||||
documentTracker.ContextChanged += (sender, args) =>
|
||||
{
|
||||
Assert.Equal(ContextChangeKind.TagHelpersChanged, args.Kind);
|
||||
called = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
documentTracker.ProjectManager_Changed(null, projectChangedArgs);
|
||||
|
||||
// Assert
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProjectManager_Changed_IgnoresUnknownProject()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectPath, ProjectManager, EditorSettingsManager, Workspace, TextBuffer);
|
||||
|
||||
var project = new AdhocWorkspace().AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), new VersionStamp(), "Test1", "TestAssembly", LanguageNames.CSharp, filePath: "C:/Some/Other/Path/TestProject.csproj"));
|
||||
var projectSnapshot = new DefaultProjectSnapshot(project);
|
||||
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
|
||||
|
||||
var called = false;
|
||||
documentTracker.ContextChanged += (sender, args) =>
|
||||
{
|
||||
called = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
documentTracker.ProjectManager_Changed(null, projectChangedArgs);
|
||||
|
||||
// Assert
|
||||
Assert.False(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Subscribe_SetsSupportedProjectAndTriggersContextChanged()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -14,40 +17,51 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
Project = new AdhocWorkspace().AddProject("Test1", LanguageNames.CSharp);
|
||||
|
||||
CompletionSource = new TaskCompletionSource<ProjectExtensibilityConfiguration>();
|
||||
ConfigurationFactory = Mock.Of<ProjectExtensibilityConfigurationFactory>(f => f.GetConfigurationAsync(It.IsAny<Project>(), default(CancellationToken)) == CompletionSource.Task);
|
||||
ConfigurationCompletionSource = new TaskCompletionSource<ProjectExtensibilityConfiguration>();
|
||||
TagHelpersCompletionSource = new TaskCompletionSource<TagHelperResolutionResult>();
|
||||
ConfigurationFactory = Mock.Of<ProjectExtensibilityConfigurationFactory>(f => f.GetConfigurationAsync(It.IsAny<Project>(), default(CancellationToken)) == ConfigurationCompletionSource.Task);
|
||||
TagHelperResolver = Mock.Of<TagHelperResolver>(f => f.GetTagHelpersAsync(It.IsAny<Project>(), default(CancellationToken)) == TagHelpersCompletionSource.Task);
|
||||
}
|
||||
|
||||
private Project Project { get; }
|
||||
|
||||
private ProjectExtensibilityConfigurationFactory ConfigurationFactory { get; }
|
||||
|
||||
private TaskCompletionSource<ProjectExtensibilityConfiguration> CompletionSource { get; }
|
||||
private TagHelperResolver TagHelperResolver { get; }
|
||||
|
||||
private TaskCompletionSource<ProjectExtensibilityConfiguration> ConfigurationCompletionSource { get; }
|
||||
|
||||
private TaskCompletionSource<TagHelperResolutionResult> TagHelpersCompletionSource { get; }
|
||||
|
||||
[ForegroundFact]
|
||||
public async Task ProcessUpdateAsync_DoesntBlockForegroundThread()
|
||||
{
|
||||
// Arrange
|
||||
var worker = new DefaultProjectSnapshotWorker(Dispatcher, ConfigurationFactory);
|
||||
var worker = new DefaultProjectSnapshotWorker(Dispatcher, ConfigurationFactory, TagHelperResolver);
|
||||
|
||||
var context = new ProjectSnapshotUpdateContext(Project);
|
||||
|
||||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
var tagHelpers = Array.Empty<TagHelperDescriptor>();
|
||||
var tagHelperResolutionResult = new TagHelperResolutionResult(tagHelpers, Array.Empty<RazorDiagnostic>());
|
||||
|
||||
// Act 1 -- We want to verify that this doesn't block the main thread
|
||||
var task = worker.ProcessUpdateAsync(context);
|
||||
|
||||
// Assert 1
|
||||
//
|
||||
// We haven't let the background task proceed yet, so this is still null.
|
||||
// We haven't let the background task proceed yet, so these are still null.
|
||||
Assert.Null(context.Configuration);
|
||||
Assert.Null(context.TagHelpers);
|
||||
|
||||
// Act 2 - Ok let's go
|
||||
CompletionSource.SetResult(configuration);
|
||||
ConfigurationCompletionSource.SetResult(configuration);
|
||||
TagHelpersCompletionSource.SetResult(tagHelperResolutionResult);
|
||||
await task;
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(configuration, context.Configuration);
|
||||
Assert.Same(tagHelpers, context.TagHelpers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
public class VsSolutionUpdatesProjectSnapshotChangeTriggerTest
|
||||
{
|
||||
[Fact]
|
||||
public void Initialize_AttachesEventSink()
|
||||
{
|
||||
// Arrange
|
||||
uint cookie;
|
||||
var buildManager = new Mock<IVsSolutionBuildManager>(MockBehavior.Strict);
|
||||
buildManager
|
||||
.Setup(b => b.AdviseUpdateSolutionEvents(It.IsAny<VsSolutionUpdatesProjectSnapshotChangeTrigger>(), out cookie))
|
||||
.Returns(VSConstants.S_OK)
|
||||
.Verifiable();
|
||||
|
||||
var services = new Mock<IServiceProvider>();
|
||||
services.Setup(s => s.GetService(It.Is<Type>(f => f == typeof(SVsSolutionBuildManager)))).Returns(buildManager.Object);
|
||||
|
||||
var trigger = new VsSolutionUpdatesProjectSnapshotChangeTrigger(services.Object, Mock.Of<TextBufferProjectService>());
|
||||
|
||||
// Act
|
||||
trigger.Initialize(Mock.Of<ProjectSnapshotManagerBase>());
|
||||
|
||||
// Assert
|
||||
buildManager.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateProjectCfg_Done_KnownProject_Invokes_ProjectBuildComplete()
|
||||
{
|
||||
// Arrange
|
||||
var expectedProjectName = "Test1";
|
||||
var expectedProjectPath = "Path/To/Project";
|
||||
|
||||
uint cookie;
|
||||
var buildManager = new Mock<IVsSolutionBuildManager>(MockBehavior.Strict);
|
||||
buildManager
|
||||
.Setup(b => b.AdviseUpdateSolutionEvents(It.IsAny<VsSolutionUpdatesProjectSnapshotChangeTrigger>(), out cookie))
|
||||
.Returns(VSConstants.S_OK);
|
||||
|
||||
var services = new Mock<IServiceProvider>();
|
||||
services.Setup(s => s.GetService(It.Is<Type>(f => f == typeof(SVsSolutionBuildManager)))).Returns(buildManager.Object);
|
||||
|
||||
var projectService = new Mock<TextBufferProjectService>();
|
||||
projectService.Setup(p => p.GetProjectName(It.IsAny<IVsHierarchy>())).Returns(expectedProjectName);
|
||||
projectService.Setup(p => p.GetProjectPath(It.IsAny<IVsHierarchy>())).Returns(expectedProjectPath);
|
||||
|
||||
var workspace = new AdhocWorkspace();
|
||||
CreateProjectInWorkspace(workspace, expectedProjectName, expectedProjectPath);
|
||||
CreateProjectInWorkspace(workspace, "Test2", "Path/To/AnotherProject");
|
||||
|
||||
var called = false;
|
||||
var projectManager = new Mock<ProjectSnapshotManagerBase>();
|
||||
projectManager.SetupGet(p => p.Workspace).Returns(workspace);
|
||||
projectManager
|
||||
.Setup(p => p.ProjectBuildComplete(It.IsAny<Project>()))
|
||||
.Callback<Project>(c =>
|
||||
{
|
||||
called = true;
|
||||
Assert.Equal(expectedProjectName, c.Name);
|
||||
});
|
||||
|
||||
var trigger = new VsSolutionUpdatesProjectSnapshotChangeTrigger(services.Object, projectService.Object);
|
||||
trigger.Initialize(projectManager.Object);
|
||||
|
||||
// Act
|
||||
trigger.UpdateProjectCfg_Done(Mock.Of<IVsHierarchy>(), Mock.Of<IVsCfg>(), Mock.Of<IVsCfg>(), 0, 0, 0);
|
||||
|
||||
// Assert
|
||||
Assert.True(called);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateProjectCfg_Done_UnknownProject_DoesNotInvoke_ProjectBuildComplete()
|
||||
{
|
||||
// Arrange
|
||||
var expectedProjectName = "Test1";
|
||||
var expectedProjectPath = "Path/To/Project";
|
||||
|
||||
uint cookie;
|
||||
var buildManager = new Mock<IVsSolutionBuildManager>(MockBehavior.Strict);
|
||||
buildManager
|
||||
.Setup(b => b.AdviseUpdateSolutionEvents(It.IsAny<VsSolutionUpdatesProjectSnapshotChangeTrigger>(), out cookie))
|
||||
.Returns(VSConstants.S_OK);
|
||||
|
||||
var services = new Mock<IServiceProvider>();
|
||||
services.Setup(s => s.GetService(It.Is<Type>(f => f == typeof(SVsSolutionBuildManager)))).Returns(buildManager.Object);
|
||||
|
||||
var projectService = new Mock<TextBufferProjectService>();
|
||||
projectService.Setup(p => p.GetProjectName(It.IsAny<IVsHierarchy>())).Returns(expectedProjectName);
|
||||
projectService.Setup(p => p.GetProjectPath(It.IsAny<IVsHierarchy>())).Returns(expectedProjectPath);
|
||||
|
||||
var workspace = new AdhocWorkspace();
|
||||
CreateProjectInWorkspace(workspace, "Test2", "Path/To/AnotherProject");
|
||||
CreateProjectInWorkspace(workspace, "Test3", "Path/To/DifferenProject");
|
||||
|
||||
var projectManager = new Mock<ProjectSnapshotManagerBase>();
|
||||
projectManager.SetupGet(p => p.Workspace).Returns(workspace);
|
||||
projectManager
|
||||
.Setup(p => p.ProjectBuildComplete(It.IsAny<Project>()))
|
||||
.Callback<Project>(c =>
|
||||
{
|
||||
throw new InvalidOperationException("This should not be called.");
|
||||
});
|
||||
|
||||
var trigger = new VsSolutionUpdatesProjectSnapshotChangeTrigger(services.Object, projectService.Object);
|
||||
trigger.Initialize(projectManager.Object);
|
||||
|
||||
// Act & Assert - Does not throw
|
||||
trigger.UpdateProjectCfg_Done(Mock.Of<IVsHierarchy>(), Mock.Of<IVsCfg>(), Mock.Of<IVsCfg>(), 0, 0, 0);
|
||||
}
|
||||
|
||||
private static AdhocWorkspace CreateProjectInWorkspace(AdhocWorkspace workspace, string name, string path)
|
||||
{
|
||||
workspace.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), new VersionStamp(), name, "TestAssembly", LanguageNames.CSharp, filePath: path));
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue