зеркало из https://github.com/dotnet/razor.git
Integration tests (#5872)
* Create VS Razor Extension Integration tests Co-authored-by: Sam Harwell <Sam.Harwell@microsoft.com>
This commit is contained in:
Родитель
7a7f566aa0
Коммит
4dba549b69
|
@ -66,7 +66,7 @@ stages:
|
|||
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open
|
||||
${{ if ne(variables['System.TeamProject'], 'public') }}:
|
||||
name: NetCore1ESPool-Internal
|
||||
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre
|
||||
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Main
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Install Node 10.x
|
||||
|
@ -108,10 +108,10 @@ stages:
|
|||
pool:
|
||||
${{ if eq(variables['System.TeamProject'], 'public') }}:
|
||||
name: NetCore1ESPool-Public
|
||||
demands: ImageOverride -equals build.windows.10.amd64.vs2019.pre.open
|
||||
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre.Open
|
||||
${{ if ne(variables['System.TeamProject'], 'public') }}:
|
||||
name: NetCore1ESPool-Internal
|
||||
demands: ImageOverride -equals build.windows.10.amd64.vs2019.pre
|
||||
demands: ImageOverride -equals Build.Windows.Amd64.VS2022.Pre
|
||||
strategy:
|
||||
matrix:
|
||||
${{ if eq(variables['System.TeamProject'], 'public') }}:
|
||||
|
@ -141,6 +141,7 @@ stages:
|
|||
- _DotNetPublishToBlobFeed : false
|
||||
- _PublishBlobFeedUrl: https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json
|
||||
- _BuildArgs: ''
|
||||
- XUNIT_LOGS: '$(Build.SourcesDirectory)\artifacts\log\Debug'
|
||||
|
||||
# Variables for internal Official builds
|
||||
- ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
<!-- Several packages share the MS.CA.Testing version -->
|
||||
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.21103.2</Tooling_MicrosoftCodeAnalysisTestingVersion>
|
||||
<MicrosoftVisualStudioShellPackagesVersion>17.0.31723.112</MicrosoftVisualStudioShellPackagesVersion>
|
||||
<MicrosoftVisualStudioPackagesVersion>17.0.448</MicrosoftVisualStudioPackagesVersion>
|
||||
<MicrosoftVisualStudioPackagesVersion>17.0.487</MicrosoftVisualStudioPackagesVersion>
|
||||
<RoslynPackageVersion>4.1.0-1.21471.13</RoslynPackageVersion>
|
||||
<VisualStudioLanguageServerProtocolVersion>17.1.2</VisualStudioLanguageServerProtocolVersion>
|
||||
</PropertyGroup>
|
||||
|
@ -105,6 +105,8 @@
|
|||
<MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>
|
||||
<OmniSharpMicrosoftExtensionsLoggingPackageVersion>2.0.0</OmniSharpMicrosoftExtensionsLoggingPackageVersion>
|
||||
<MicrosoftVisualStudioEditorPackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioEditorPackageVersion>
|
||||
<MicrosoftVisualStudioExtensibilityTestingXunitVersion>0.1.122-beta</MicrosoftVisualStudioExtensibilityTestingXunitVersion>
|
||||
<MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>$(MicrosoftVisualStudioExtensibilityTestingXunitVersion)</MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion>
|
||||
<MicrosoftVisualStudioLanguagePackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioLanguagePackageVersion>
|
||||
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>$(MicrosoftVisualStudioPackagesVersion)</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
|
||||
<MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>17.1.11</MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>
|
||||
|
@ -130,6 +132,7 @@
|
|||
<MonoDevelopSdkPackageVersion>1.0.15</MonoDevelopSdkPackageVersion>
|
||||
<MoqPackageVersion>4.16.0</MoqPackageVersion>
|
||||
<NerdbankStreamsPackageVersion>2.7.74</NerdbankStreamsPackageVersion>
|
||||
<NuGetSolutionRestoreManagerInteropVersion>4.8.0</NuGetSolutionRestoreManagerInteropVersion>
|
||||
<OmniSharpExtensionsLanguageServerPackageVersion>0.19.5</OmniSharpExtensionsLanguageServerPackageVersion>
|
||||
<OmniSharpExtensionsLanguageProtocolPackageVersion>$(OmniSharpExtensionsLanguageServerPackageVersion)</OmniSharpExtensionsLanguageProtocolPackageVersion>
|
||||
<OmniSharpMSBuildPackageVersion>1.37.13</OmniSharpMSBuildPackageVersion>
|
||||
|
|
|
@ -93,6 +93,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
..\..\.editorconfig = ..\..\.editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Razor.Integration.Test", "test\Microsoft.VisualStudio.Razor.Integration.Test\Microsoft.VisualStudio.Razor.Integration.Test.csproj", "{8CEC0991-259F-4313-B3EF-E398F2B40E61}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -405,6 +407,14 @@ Global
|
|||
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -448,6 +458,7 @@ Global
|
|||
{1C7BBF16-3507-4A23-91B4-9EEA03409C72} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{39233703-B752-43AC-AD86-E9D3E61B4AD9} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
{17C4A6DF-3AA5-43FE-8A0E-53DF14340446} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
|
||||
{8CEC0991-259F-4313-B3EF-E398F2B40E61} = {92463391-81BE-462B-AC3C-78C6C760741F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}
|
||||
|
|
|
@ -52,13 +52,13 @@ export class RazorLogger implements vscode.Disposable {
|
|||
this.logAlways(warningPrefixedMessage);
|
||||
}
|
||||
|
||||
public logError(message: string, error: Error) {
|
||||
// Always log errors
|
||||
const errorPrefixedMessage = `(Error) ${message}
|
||||
${error.message}
|
||||
Stack Trace:
|
||||
${error.stack}`;
|
||||
this.logAlways(errorPrefixedMessage);
|
||||
public logError(message: string, error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
this.logErrorInternal(message, error);
|
||||
} else {
|
||||
const errorMsg = String(error);
|
||||
this.logErrorInternal(message, Error(errorMsg));
|
||||
}
|
||||
}
|
||||
|
||||
public logMessage(message: string) {
|
||||
|
@ -77,6 +77,15 @@ ${error.stack}`;
|
|||
this.outputChannel.dispose();
|
||||
}
|
||||
|
||||
private logErrorInternal(message: string, error: Error) {
|
||||
// Always log errors
|
||||
const errorPrefixedMessage = `(Error) ${message}
|
||||
${error.message}
|
||||
Stack Trace:
|
||||
${error.stack}`;
|
||||
this.logAlways(errorPrefixedMessage);
|
||||
}
|
||||
|
||||
private logWithMarker(message: string) {
|
||||
const timeString = new Date().toLocaleTimeString();
|
||||
const markedMessage = `[Client - ${timeString}] ${message}`;
|
||||
|
|
|
@ -27,12 +27,26 @@ export class TelemetryReporter {
|
|||
this.eventStream.post(traceLevelEvent);
|
||||
}
|
||||
|
||||
public reportErrorOnServerStart(error: Error) {
|
||||
this.reportError('VSCode.Razor.ErrorOnServerStart', error);
|
||||
public reportErrorOnServerStart(error: unknown) {
|
||||
let realError;
|
||||
if (error instanceof Error) {
|
||||
realError = error;
|
||||
} else {
|
||||
realError = Error(String(error));
|
||||
}
|
||||
|
||||
this.reportError('VSCode.Razor.ErrorOnServerStart', realError);
|
||||
}
|
||||
|
||||
public reportErrorOnActivation(error: Error) {
|
||||
this.reportError('VSCode.Razor.ErrorOnActivation', error);
|
||||
public reportErrorOnActivation(error: unknown) {
|
||||
let realError;
|
||||
if (error instanceof Error) {
|
||||
realError = error;
|
||||
} else {
|
||||
realError = Error(String(error));
|
||||
}
|
||||
|
||||
this.reportError('VSCode.Razor.ErrorOnActivation', realError);
|
||||
}
|
||||
|
||||
public reportDebugLanguageServer() {
|
||||
|
|
|
@ -7,6 +7,8 @@ using System;
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
|
@ -59,16 +61,33 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
return false;
|
||||
}
|
||||
|
||||
public async Task<string> ReadAllTextAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (var reader = new StreamReader(OpenRead()))
|
||||
{
|
||||
var contents = await reader.ReadToEndAsync();
|
||||
|
||||
return NormalizeContents(contents);
|
||||
}
|
||||
}
|
||||
|
||||
public string ReadAllText()
|
||||
{
|
||||
using (var reader = new StreamReader(OpenRead()))
|
||||
{
|
||||
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
|
||||
// since all the unit tests rely on the assumption that the files will have \r\n endings.
|
||||
return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n");
|
||||
var contents = reader.ReadToEnd();
|
||||
|
||||
return NormalizeContents(contents);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeContents(string contents)
|
||||
{
|
||||
// The .Replace() calls normalize line endings, in case you get \n instead of \r\n
|
||||
// since all the unit tests rely on the assumption that the files will have \r\n endings.
|
||||
return contents.Replace("\r", "").Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the specified path.
|
||||
/// </summary>
|
||||
|
|
|
@ -10,9 +10,10 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
public static class TestProject
|
||||
{
|
||||
public static string GetProjectDirectory(Type type)
|
||||
public static string GetProjectDirectory(Type type, bool useCurrentDirectory = false)
|
||||
{
|
||||
var repoRoot = SearchUp(AppContext.BaseDirectory, "global.json");
|
||||
var baseDir = useCurrentDirectory ? Directory.GetCurrentDirectory() : AppContext.BaseDirectory;
|
||||
var repoRoot = SearchUp(baseDir, "global.json");
|
||||
var assemblyName = type.Assembly.GetName().Name;
|
||||
var projectDirectory = Path.Combine(repoRoot, "src", "Razor", "test", assemblyName);
|
||||
if (!Directory.Exists(projectDirectory) &&
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public abstract class AbstractEditorTest : AbstractIntegrationTest
|
||||
{
|
||||
private readonly string? _solutionName;
|
||||
private readonly string? _projectName;
|
||||
private readonly string? _projectTemplate;
|
||||
|
||||
protected AbstractEditorTest()
|
||||
{
|
||||
}
|
||||
|
||||
protected AbstractEditorTest(string solutionName)
|
||||
: this(solutionName, WellKnownProjectTemplates.BlazorProject, "BlazorProject")
|
||||
{
|
||||
}
|
||||
|
||||
protected AbstractEditorTest(string solutionName, string projectTemplate, string projectName)
|
||||
{
|
||||
_solutionName = solutionName;
|
||||
_projectTemplate = projectTemplate;
|
||||
_projectName = projectName;
|
||||
}
|
||||
|
||||
protected abstract string LanguageName { get; }
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
|
||||
if (_solutionName is not null)
|
||||
{
|
||||
RazorDebug.AssertNotNull(_projectTemplate);
|
||||
RazorDebug.AssertNotNull(_projectName);
|
||||
|
||||
await TestServices.SolutionExplorer.CreateSolutionAsync(_solutionName, HangMitigatingCancellationToken);
|
||||
await TestServices.SolutionExplorer.AddProjectAsync(_projectName, _projectTemplate, LanguageName, HangMitigatingCancellationToken);
|
||||
await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(ProjectName, HangMitigatingCancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.Extensibility.Testing;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
// TODO: Start collecting LogFiles on failure
|
||||
|
||||
/// <remarks>
|
||||
/// The following is the xunit execution order:
|
||||
///
|
||||
/// <list type="number">
|
||||
/// <item><description>Instance constructor</description></item>
|
||||
/// <item><description><see cref="IAsyncLifetime.InitializeAsync"/></description></item>
|
||||
/// <item><description><see cref="BeforeAfterTestAttribute.Before"/></description></item>
|
||||
/// <item><description>Test method</description></item>
|
||||
/// <item><description><see cref="BeforeAfterTestAttribute.After"/></description></item>
|
||||
/// <item><description><see cref="IAsyncLifetime.DisposeAsync"/></description></item>
|
||||
/// <item><description><see cref="IDisposable.Dispose"/></description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[IdeSettings(MinVersion = VisualStudioVersion.VS2022, RootSuffix = "RoslynDev")]
|
||||
public abstract class AbstractIntegrationTest : AbstractIdeIntegrationTest
|
||||
{
|
||||
protected const string ProjectName = "TestProj";
|
||||
protected const string SolutionName = "TestSolution";
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public abstract class AbstractRazorEditorTest : AbstractEditorTest
|
||||
{
|
||||
internal const string BlazorProjectName = "BlazorProject";
|
||||
|
||||
private static readonly string s_pagesDir = Path.Combine("Pages");
|
||||
private static readonly string s_sharedDir = Path.Combine("Shared");
|
||||
internal static readonly string CounterRazorFile = Path.Combine(s_pagesDir, "Counter.razor");
|
||||
internal static readonly string SemanticTokensFile = Path.Combine(s_pagesDir, "SemanticTokens.razor");
|
||||
internal static readonly string MainLayoutFile = Path.Combine(s_sharedDir, "MainLayout.razor");
|
||||
internal static readonly string ImportsRazorFile = "_Imports.razor";
|
||||
|
||||
protected override string LanguageName => LanguageNames.Razor;
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
|
||||
await TestServices.SolutionExplorer.CreateSolutionAsync("BlazorSolution", HangMitigatingCancellationToken);
|
||||
await TestServices.SolutionExplorer.AddProjectAsync("BlazorProject", WellKnownProjectTemplates.BlazorProject, groupId: WellKnownProjectTemplates.GroupIdentifiers.Server, templateId: null, LanguageName, HangMitigatingCancellationToken);
|
||||
await TestServices.SolutionExplorer.RestoreNuGetPackagesAsync(HangMitigatingCancellationToken);
|
||||
await TestServices.Workspace.WaitForProjectSystemAsync(HangMitigatingCancellationToken);
|
||||
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LanguageServer, HangMitigatingCancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
|
||||
{
|
||||
internal static class IBufferGraphExtensions
|
||||
{
|
||||
public static SnapshotSpan? MapUpOrDownToFirstMatch(this IBufferGraph bufferGraph, SnapshotSpan span, Predicate<ITextSnapshot> match)
|
||||
{
|
||||
var spans = bufferGraph.MapDownToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
|
||||
if (!spans.Any())
|
||||
{
|
||||
spans = bufferGraph.MapUpToFirstMatch(span, SpanTrackingMode.EdgeExclusive, match);
|
||||
}
|
||||
|
||||
return spans.Select(s => (SnapshotSpan?)s).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
|
||||
{
|
||||
internal static class ITextViewExtensions
|
||||
{
|
||||
public static SnapshotPoint? GetCaretPoint(this ITextView textView, Predicate<ITextSnapshot> match)
|
||||
{
|
||||
var caret = textView.Caret.Position;
|
||||
var span = textView.BufferGraph.MapUpOrDownToFirstMatch(new SnapshotSpan(caret.BufferPosition, 0), match);
|
||||
if (span.HasValue)
|
||||
{
|
||||
return span.Value.Start;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ITextBuffer? GetBufferContainingCaret(this ITextView textView, string contentType = StandardContentTypeNames.Text)
|
||||
{
|
||||
var point = GetCaretPoint(textView, s => s.ContentType.IsOfType(contentType));
|
||||
return point.HasValue ? point.Value.Snapshot.TextBuffer : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TextManager.Interop;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
|
||||
{
|
||||
internal static class IVsTextManagerExtensions
|
||||
{
|
||||
public static Task<IVsTextView> GetActiveViewAsync(this IVsTextManager textManager, JoinableTaskFactory joinableTaskFactory, CancellationToken cancellationToken)
|
||||
=> textManager.GetActiveViewAsync(joinableTaskFactory, mustHaveFocus: true, buffer: null, cancellationToken);
|
||||
|
||||
public static async Task<IVsTextView> GetActiveViewAsync(this IVsTextManager textManager, JoinableTaskFactory joinableTaskFactory, bool mustHaveFocus, IVsTextBuffer? buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
await joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
ErrorHandler.ThrowOnFailure(textManager.GetActiveView(fMustHaveFocus: mustHaveFocus ? 1 : 0, pBuffer: buffer, ppView: out var vsTextView));
|
||||
|
||||
return vsTextView;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Editor;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.TextManager.Interop;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.Extensions
|
||||
{
|
||||
internal static class IVsTextViewExtensions
|
||||
{
|
||||
public static async Task<IWpfTextViewHost> GetTextViewHostAsync(this IVsTextView textView, JoinableTaskFactory joinableTaskFactory, CancellationToken cancellationToken)
|
||||
{
|
||||
await joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
ErrorHandler.ThrowOnFailure(((IVsUserData)textView).GetData(DefGuidList.guidIWpfTextViewHost, out var wpfTextViewHost));
|
||||
return (IWpfTextViewHost)wpfTextViewHost;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
|
||||
using Microsoft.VisualStudio.OLE.Interop;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
public async Task<ITextSnapshot> GetActiveSnapshotAsync(CancellationToken cancellationToken)
|
||||
=> (await GetActiveTextViewAsync(cancellationToken)).TextSnapshot;
|
||||
|
||||
public async Task ActivateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
dte.ActiveDocument.Activate();
|
||||
}
|
||||
|
||||
public async Task<bool> IsUseSuggestionModeOnAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var textView = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var subjectBuffer = textView.GetBufferContainingCaret();
|
||||
Assumes.Present(subjectBuffer);
|
||||
|
||||
var options = textView.Options.GlobalOptions;
|
||||
EditorOptionKey<bool> optionKey;
|
||||
bool defaultOption;
|
||||
if (IsDebuggerTextView(textView))
|
||||
{
|
||||
optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName);
|
||||
defaultOption = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
optionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInCompletionOptionName);
|
||||
defaultOption = false;
|
||||
}
|
||||
|
||||
if (!options.IsOptionDefined(optionKey, localScopeOnly: false))
|
||||
{
|
||||
return defaultOption;
|
||||
}
|
||||
|
||||
return options.GetOptionValue(optionKey);
|
||||
|
||||
static bool IsDebuggerTextView(IWpfTextView textView)
|
||||
{
|
||||
return textView.Roles.Contains("DEBUGVIEW");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetUseSuggestionModeAsync(bool value, CancellationToken cancellationToken)
|
||||
{
|
||||
if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
|
||||
{
|
||||
var dispatcher = await GetRequiredGlobalServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>(cancellationToken);
|
||||
ErrorHandler.ThrowOnFailure(dispatcher.Exec(typeof(VSConstants.VSStd2KCmdID).GUID, (uint)VSConstants.VSStd2KCmdID.ToggleConsumeFirstCompletionMode, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, IntPtr.Zero, IntPtr.Zero));
|
||||
|
||||
if (await IsUseSuggestionModeOnAsync(cancellationToken) != value)
|
||||
{
|
||||
throw new InvalidOperationException($"Edit.ToggleCompletionMode did not leave the editor in the expected state.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!value)
|
||||
{
|
||||
// For blocking completion mode, make sure we don't have responsive completion interfering when
|
||||
// integration tests run slowly.
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
var options = view.Options.GlobalOptions;
|
||||
options.SetOptionValue(DefaultOptions.ResponsiveCompletionOptionId, false);
|
||||
|
||||
var latencyGuardOptionKey = new EditorOptionKey<bool>("EnableTypingLatencyGuard");
|
||||
options.SetOptionValue(latencyGuardOptionKey, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.Extensions;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
public async Task MoveCaretAsync(int position, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var subjectBuffer = view.GetBufferContainingCaret();
|
||||
Assumes.Present(subjectBuffer);
|
||||
|
||||
var point = new SnapshotPoint(subjectBuffer.CurrentSnapshot, position);
|
||||
|
||||
view.Caret.MoveTo(point);
|
||||
}
|
||||
|
||||
public Task PlaceCaretAsync(string marker, int charsOffset, CancellationToken cancellationToken)
|
||||
=> PlaceCaretAsync(marker, charsOffset, occurrence: 0, extendSelection: false, selectBlock: false, cancellationToken);
|
||||
|
||||
public async Task PlaceCaretAsync(
|
||||
string marker,
|
||||
int charsOffset,
|
||||
int occurrence,
|
||||
bool extendSelection,
|
||||
bool selectBlock,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
dte.Find.FindWhat = marker;
|
||||
dte.Find.MatchCase = true;
|
||||
dte.Find.MatchInHiddenText = true;
|
||||
dte.Find.Target = EnvDTE.vsFindTarget.vsFindTargetCurrentDocument;
|
||||
dte.Find.Action = EnvDTE.vsFindAction.vsFindActionFind;
|
||||
|
||||
var originalPosition = await GetCaretPositionAsync(cancellationToken);
|
||||
view.Caret.MoveTo(new SnapshotPoint(view.GetBufferContainingCaret()!.CurrentSnapshot, 0));
|
||||
|
||||
if (occurrence > 0)
|
||||
{
|
||||
var result = EnvDTE.vsFindResult.vsFindResultNotFound;
|
||||
for (var i = 0; i < occurrence; i++)
|
||||
{
|
||||
result = dte.Find.Execute();
|
||||
}
|
||||
|
||||
if (result != EnvDTE.vsFindResult.vsFindResultFound)
|
||||
{
|
||||
throw new Exception("Occurrence " + occurrence + " of marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = dte.Find.Execute();
|
||||
if (result != EnvDTE.vsFindResult.vsFindResultFound)
|
||||
{
|
||||
throw new Exception("Marker '" + marker + "' not found in text: " + view.TextSnapshot.GetText());
|
||||
}
|
||||
}
|
||||
|
||||
if (charsOffset > 0)
|
||||
{
|
||||
for (var i = 0; i < charsOffset - 1; i++)
|
||||
{
|
||||
view.Caret.MoveToNextCaretPosition();
|
||||
}
|
||||
|
||||
view.Selection.Clear();
|
||||
}
|
||||
|
||||
if (charsOffset < 0)
|
||||
{
|
||||
// On the first negative charsOffset, move to anchor-point position, as if the user hit the LEFT key
|
||||
view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, view.Selection.AnchorPoint.Position.Position));
|
||||
|
||||
for (var i = 0; i < -charsOffset - 1; i++)
|
||||
{
|
||||
view.Caret.MoveToPreviousCaretPosition();
|
||||
}
|
||||
|
||||
view.Selection.Clear();
|
||||
}
|
||||
|
||||
if (extendSelection)
|
||||
{
|
||||
var newPosition = view.Selection.ActivePoint.Position.Position;
|
||||
view.Selection.Select(new VirtualSnapshotPoint(view.TextSnapshot, originalPosition), new VirtualSnapshotPoint(view.TextSnapshot, newPosition));
|
||||
view.Selection.Mode = selectBlock ? TextSelectionMode.Box : TextSelectionMode.Stream;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> GetCaretPositionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var subjectBuffer = view.GetBufferContainingCaret();
|
||||
Assumes.Present(subjectBuffer);
|
||||
|
||||
var bufferPosition = view.Caret.Position.BufferPosition;
|
||||
return bufferPosition.Position;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Classification;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
/// <summary>
|
||||
/// Waits for any semantic classifications to be available on the active TextView, and for at least one of the
|
||||
/// <paramref name="expectedClassification"/> if provided.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token.</param>
|
||||
/// <param name="expectedClassification">The classification to wait for, if any.</param>
|
||||
/// <param name="count">The number of the given classification to expect.</param>
|
||||
/// <returns>A <see cref="Task"/> which completes when classification is "ready".</returns>
|
||||
public async Task WaitForClassificationAsync(CancellationToken cancellationToken, string expectedClassification = "RazorComponentElement", int count = 1)
|
||||
{
|
||||
var textView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);
|
||||
var classifier = await GetClassifierAsync(textView, cancellationToken);
|
||||
|
||||
using var semaphore = new SemaphoreSlim(1);
|
||||
await semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
classifier.ClassificationChanged += Classifier_ClassificationChanged;
|
||||
|
||||
// Check that we're not ALREADY changed
|
||||
if (HasClassification(classifier, textView, expectedClassification, count))
|
||||
{
|
||||
semaphore.Release();
|
||||
classifier.ClassificationChanged -= Classifier_ClassificationChanged;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await semaphore.WaitAsync(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
classifier.ClassificationChanged -= Classifier_ClassificationChanged;
|
||||
}
|
||||
|
||||
void Classifier_ClassificationChanged(object sender, ClassificationChangedEventArgs e)
|
||||
{
|
||||
var classifications = GetClassifications(classifier, textView);
|
||||
|
||||
if (HasClassification(classifier, textView, expectedClassification, count))
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
static bool HasClassification(IClassifier classifier, ITextView textView, string expectedClassification, int count)
|
||||
{
|
||||
var classifications = GetClassifications(classifier, textView);
|
||||
return classifications.Where(
|
||||
c => c.ClassificationType.BaseTypes.Any(bT => bT is ILayeredClassificationType layered &&
|
||||
layered.Layer == ClassificationLayer.Semantic &&
|
||||
layered.Classification == expectedClassification)).Count() >= count;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task VerifyGetClassificationsAsync(IEnumerable<ClassificationSpan> expectedClassifications, CancellationToken cancellationToken)
|
||||
{
|
||||
var actualClassifications = await GetClassificationsAsync(cancellationToken);
|
||||
var actualArray = actualClassifications.ToArray();
|
||||
var expectedArray = expectedClassifications.ToArray();
|
||||
|
||||
for (var i = 0; i < actualArray.Length; i++)
|
||||
{
|
||||
var actualClassification = actualArray[i];
|
||||
var expectedClassification = expectedArray[i];
|
||||
|
||||
if (actualClassification.ClassificationType.BaseTypes.Count() > 1)
|
||||
{
|
||||
Assert.Equal(expectedClassification.Span, actualClassification.Span);
|
||||
var semanticBaseTypes = actualClassification.ClassificationType.BaseTypes.Where(t => t is ILayeredClassificationType layered && layered.Layer == ClassificationLayer.Semantic);
|
||||
if (semanticBaseTypes.Count() == 1)
|
||||
{
|
||||
Assert.Equal(expectedClassification.ClassificationType.Classification, semanticBaseTypes.First().Classification);
|
||||
}
|
||||
else if (semanticBaseTypes.Count() > 1)
|
||||
{
|
||||
Assert.Contains(expectedClassification.ClassificationType.Classification, semanticBaseTypes.Select(s => s.Classification));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(false, "Did not have semantic basetype");
|
||||
}
|
||||
}
|
||||
else if (!expectedClassification.Span.Span.Equals(actualClassification.Span.Span)
|
||||
|| !string.Equals(expectedClassification.ClassificationType.Classification, actualClassification.ClassificationType.Classification))
|
||||
{
|
||||
Assert.Equal(expectedClassification.Span, actualClassification.Span);
|
||||
Assert.Equal(expectedClassification.ClassificationType.Classification, actualClassification.ClassificationType.Classification);
|
||||
|
||||
Assert.True(false,
|
||||
$"i: {i}" +
|
||||
$"expected: {expectedClassification.Span} {expectedClassification.ClassificationType.Classification} " +
|
||||
$"actual: {actualClassification.Span} {actualClassification.ClassificationType.Classification}");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(expectedArray.Length, actualArray.Length);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ClassificationSpan>> GetClassificationsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var textView = await GetActiveTextViewAsync(cancellationToken);
|
||||
var classifier = await GetClassifierAsync(textView, cancellationToken);
|
||||
return GetClassifications(classifier, textView);
|
||||
}
|
||||
|
||||
private static IEnumerable<ClassificationSpan> GetClassifications(IClassifier classifier, ITextView textView)
|
||||
{
|
||||
var selectionSpan = new SnapshotSpan(textView.TextSnapshot, new Span(0, textView.TextSnapshot.Length));
|
||||
|
||||
var classifiedSpans = classifier.GetClassificationSpans(selectionSpan);
|
||||
return classifiedSpans;
|
||||
}
|
||||
|
||||
private async Task<IClassifier> GetClassifierAsync(IWpfTextView textView, CancellationToken cancellationToken)
|
||||
{
|
||||
var classifierService = await GetComponentModelServiceAsync<IViewClassifierAggregatorService>(cancellationToken);
|
||||
|
||||
return classifierService.GetClassifier(textView);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
public async Task DismissCompletionSessionsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var asyncBroker = await GetComponentModelServiceAsync<IAsyncCompletionBroker>(cancellationToken);
|
||||
var session = asyncBroker.GetSession(view);
|
||||
if (session is not null && !session.IsDismissed)
|
||||
{
|
||||
session.Dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.OLE.Interop;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
public async Task DismissLightBulbSessionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
|
||||
broker.DismissSession(view);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SuggestedActionSet>> InvokeCodeActionListAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.SolutionCrawler, cancellationToken);
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.DiagnosticService, cancellationToken);
|
||||
|
||||
var lightbulbs = await ShowLightBulbAsync(cancellationToken);
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LightBulb, cancellationToken);
|
||||
return lightbulbs;
|
||||
}
|
||||
|
||||
public async Task InvokeCodeActionAsync(ISuggestedAction codeAction, CancellationToken cancellationToken)
|
||||
{
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
codeAction.Invoke(cancellationToken);
|
||||
|
||||
// ISuggestedAction.Invoke does not dismiss the session, so we must do it manually
|
||||
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
|
||||
broker.DismissSession(view);
|
||||
}
|
||||
|
||||
public async Task<bool> IsLightBulbSessionExpandedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
|
||||
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
|
||||
if (!broker.IsLightBulbSessionActive(view))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var session = broker.GetSession(view);
|
||||
if (session is null || !session.IsExpanded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<SuggestedActionSet>> ShowLightBulbAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var shell = await GetRequiredGlobalServiceAsync<SVsUIShell, IVsUIShell>(cancellationToken);
|
||||
var cmdGroup = typeof(VSConstants.VSStd14CmdID).GUID;
|
||||
var cmdExecOpt = OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER;
|
||||
|
||||
var cmdID = VSConstants.VSStd14CmdID.ShowQuickFixes;
|
||||
object? obj = null;
|
||||
shell.PostExecCommand(cmdGroup, (uint)cmdID, (uint)cmdExecOpt, ref obj);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
var broker = await GetComponentModelServiceAsync<ILightBulbBroker>(cancellationToken);
|
||||
|
||||
await LightBulbHelper.WaitForLightBulbSessionAsync(broker, view, cancellationToken);
|
||||
return await LightBulbHelper.WaitForItemsAsync(broker, view, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class EditorInProcess
|
||||
{
|
||||
public async Task SetTextAsync(string text, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
var textSnapshot = view.TextSnapshot;
|
||||
var replacementSpan = new SnapshotSpan(textSnapshot, 0, textSnapshot.Length);
|
||||
view.TextBuffer.Replace(replacementSpan, text);
|
||||
}
|
||||
|
||||
public async Task VerifyTextContainsAsync(string text, CancellationToken cancellationToken)
|
||||
{
|
||||
var view = await GetActiveTextViewAsync(cancellationToken);
|
||||
var content = view.TextBuffer.CurrentSnapshot.GetText();
|
||||
Assert.Contains(text, content);
|
||||
}
|
||||
|
||||
private async Task WaitForProjectReadyAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.LanguageServer, cancellationToken);
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.Workspace, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
|
||||
{
|
||||
internal static class FeatureAttribute
|
||||
{
|
||||
public const string AutomaticEndConstructCorrection = nameof(AutomaticEndConstructCorrection);
|
||||
public const string AutomaticPairCompletion = nameof(AutomaticPairCompletion);
|
||||
public const string BraceHighlighting = nameof(BraceHighlighting);
|
||||
public const string CallHierarchy = nameof(CallHierarchy);
|
||||
public const string Classification = nameof(Classification);
|
||||
public const string CodeModel = nameof(CodeModel);
|
||||
public const string CodeDefinitionWindow = nameof(CodeDefinitionWindow);
|
||||
public const string CompletionSet = nameof(CompletionSet);
|
||||
public const string DesignerAttributes = nameof(DesignerAttributes);
|
||||
public const string DiagnosticService = nameof(DiagnosticService);
|
||||
public const string EncapsulateField = nameof(EncapsulateField);
|
||||
public const string ErrorList = nameof(ErrorList);
|
||||
public const string ErrorSquiggles = nameof(ErrorSquiggles);
|
||||
public const string EventHookup = nameof(EventHookup);
|
||||
public const string FindReferences = nameof(FindReferences);
|
||||
public const string GlobalOperation = nameof(GlobalOperation);
|
||||
public const string GoToImplementation = nameof(GoToImplementation);
|
||||
public const string GraphProvider = nameof(GraphProvider);
|
||||
public const string InfoBar = nameof(InfoBar);
|
||||
public const string InlineDiagnostics = nameof(InlineDiagnostics);
|
||||
public const string InheritanceMargin = nameof(InheritanceMargin);
|
||||
public const string InlineHints = nameof(InlineHints);
|
||||
public const string InteractiveEvaluator = nameof(InteractiveEvaluator);
|
||||
public const string KeywordHighlighting = nameof(KeywordHighlighting);
|
||||
public const string LibraryManager = nameof(LibraryManager);
|
||||
public const string LightBulb = nameof(LightBulb);
|
||||
public const string LineSeparators = nameof(LineSeparators);
|
||||
public const string NavigableSymbols = nameof(NavigableSymbols);
|
||||
public const string NavigateTo = nameof(NavigateTo);
|
||||
public const string NavigationBar = nameof(NavigationBar);
|
||||
public const string Outlining = nameof(Outlining);
|
||||
public const string PackageInstaller = nameof(PackageInstaller);
|
||||
public const string PersistentStorage = nameof(PersistentStorage);
|
||||
public const string QuickInfo = nameof(QuickInfo);
|
||||
public const string ReferenceHighlighting = nameof(ReferenceHighlighting);
|
||||
public const string Rename = nameof(Rename);
|
||||
public const string RenameTracking = nameof(RenameTracking);
|
||||
public const string SolutionChecksumUpdater = nameof(SolutionChecksumUpdater);
|
||||
public const string SourceGenerators = nameof(SourceGenerators);
|
||||
public const string RuleSetEditor = nameof(RuleSetEditor);
|
||||
public const string SignatureHelp = nameof(SignatureHelp);
|
||||
public const string Snippets = nameof(Snippets);
|
||||
public const string SolutionCrawler = nameof(SolutionCrawler);
|
||||
public const string Telemetry = nameof(Telemetry);
|
||||
public const string TodoCommentList = nameof(TodoCommentList);
|
||||
public const string LanguageServer = nameof(LanguageServer);
|
||||
public const string Workspace = nameof(Workspace);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
|
||||
{
|
||||
internal static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// This method will retry the asynchronous action represented by <paramref name="action"/>,
|
||||
/// waiting for <paramref name="delay"/> time after each retry. If a given retry returns a value
|
||||
/// other than the default value of <typeparamref name="T"/>, this value is returned.
|
||||
/// </summary>
|
||||
/// <param name="action">the asynchronous action to retry</param>
|
||||
/// <param name="delay">the amount of time to wait between retries</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <typeparam name="T">type of return value</typeparam>
|
||||
/// <returns>the return value of <paramref name="action"/></returns>
|
||||
public static Task<T?> RetryAsync<T>(Func<CancellationToken, Task<T>> action, TimeSpan delay, CancellationToken cancellationToken)
|
||||
{
|
||||
return RetryCoreAsync(
|
||||
async cancellationToken =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await action(cancellationToken);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// Devenv can throw COMExceptions if it's busy when we make DTE calls.
|
||||
return default;
|
||||
}
|
||||
},
|
||||
delay,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<T> RetryCoreAsync<T>(Func<CancellationToken, Task<T>> action, TimeSpan delay, CancellationToken cancellationToken)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var retval = await action(cancellationToken);
|
||||
if (!Equals(default(T), retval))
|
||||
{
|
||||
return retval;
|
||||
}
|
||||
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
|
||||
{
|
||||
public static class LightBulbHelper
|
||||
{
|
||||
public static async Task<bool> WaitForLightBulbSessionAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken)
|
||||
{
|
||||
var startTime = DateTimeOffset.Now;
|
||||
|
||||
var active = await Helper.RetryAsync(async cancellationToken =>
|
||||
{
|
||||
if (broker.IsLightBulbSessionActive(view))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new InvalidOperationException("Expected a light bulb session to appear.");
|
||||
}
|
||||
|
||||
if (broker.IsLightBulbSessionActive(view))
|
||||
{
|
||||
var session = broker.GetSession(view);
|
||||
var hasSuggestedActions = await broker.HasSuggestedActionsAsync(session.ActionCategories, view, cancellationToken);
|
||||
|
||||
return hasSuggestedActions;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, TimeSpan.FromMilliseconds(1), cancellationToken);
|
||||
|
||||
if (!active)
|
||||
return false;
|
||||
|
||||
await WaitForItemsAsync(broker, view, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<SuggestedActionSet>> WaitForItemsAsync(ILightBulbBroker broker, IWpfTextView view, CancellationToken cancellationToken)
|
||||
{
|
||||
var activeSession = broker.GetSession(view);
|
||||
if (activeSession is null)
|
||||
{
|
||||
var bufferType = view.TextBuffer.ContentType.DisplayName;
|
||||
throw new InvalidOperationException($"No expanded light bulb session found after View.ShowSmartTag. Buffer content type={bufferType}");
|
||||
}
|
||||
|
||||
var asyncSession = (IAsyncLightBulbSession)activeSession;
|
||||
var tcs = new TaskCompletionSource<List<SuggestedActionSet>>();
|
||||
|
||||
void Handler(object s, SuggestedActionsUpdatedArgs e)
|
||||
{
|
||||
// ignore these. we care about when the lightbulb items are all completed.
|
||||
if (e.Status == QuerySuggestedActionCompletionStatus.InProgress)
|
||||
return;
|
||||
|
||||
if (e.Status == QuerySuggestedActionCompletionStatus.Completed || e.Status == QuerySuggestedActionCompletionStatus.CompletedWithoutData)
|
||||
tcs.SetResult(e.ActionSets.ToList());
|
||||
else
|
||||
tcs.SetException(new InvalidOperationException($"Light bulb transitioned to non-complete state: {e.Status}"));
|
||||
|
||||
asyncSession.SuggestedActionsUpdated -= Handler;
|
||||
}
|
||||
|
||||
asyncSession.SuggestedActionsUpdated += Handler;
|
||||
|
||||
asyncSession.Dismissed += (_, _) => tcs.TrySetCanceled(new CancellationToken(true));
|
||||
|
||||
if (asyncSession.IsDismissed)
|
||||
tcs.TrySetCanceled(new CancellationToken(true));
|
||||
|
||||
// Calling PopulateWithData ensures the underlying session will call SuggestedActionsUpdated at least once
|
||||
// with the latest data computed. This is needed so that if the lightbulb computation is already complete
|
||||
// that we hear about the results.
|
||||
asyncSession.PopulateWithData(overrideRequestedActionCategories: null, operationContext: null);
|
||||
|
||||
return await tcs.Task.WithCancellation(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
|
||||
{
|
||||
internal static class MarkupTestFile
|
||||
{
|
||||
internal static void GetPosition(string markupCode, out string code, out int caretPosition)
|
||||
{
|
||||
caretPosition = markupCode.IndexOf("$$");
|
||||
code = markupCode.Replace("$$", "");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.TextManager.Interop;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using NuGet.SolutionRestoreManager;
|
||||
using Xunit;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class SolutionExplorerInProcess
|
||||
{
|
||||
public async Task CreateSolutionAsync(string solutionName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var solutionPath = CreateTemporaryPath();
|
||||
await CreateSolutionAsync(solutionPath, solutionName, cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddProjectAsync(string projectName, string projectTemplate, string languageName, CancellationToken cancellationToken)
|
||||
=> AddProjectAsync(projectName, projectTemplate, groupId: null, templateId: null, languageName, cancellationToken);
|
||||
|
||||
public async Task AddProjectAsync(string projectName, string projectTemplate, string? groupId, string? templateId, string languageName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var projectPath = Path.Combine(await GetDirectoryNameAsync(cancellationToken), projectName);
|
||||
var projectTemplatePath = await GetProjectTemplatePathAsync(projectTemplate, ConvertLanguageName(languageName), cancellationToken);
|
||||
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution6>(cancellationToken);
|
||||
|
||||
var args = new List<object>();
|
||||
if (groupId is not null)
|
||||
args.Add($"$groupid$={groupId}");
|
||||
if (groupId is not null)
|
||||
args.Add($"$templateid$={templateId}");
|
||||
|
||||
ErrorHandler.ThrowOnFailure(solution.AddNewProjectFromTemplate(projectTemplatePath, args.Any() ? args.ToArray() : null, null, projectPath, projectName, null, out _));
|
||||
}
|
||||
|
||||
public async Task RestoreNuGetPackagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
var solution = (EnvDTE80.Solution2)dte.Solution;
|
||||
foreach (var project in solution.Projects.OfType<EnvDTE.Project>())
|
||||
{
|
||||
await RestoreNuGetPackagesAsync(project.FullName, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RestoreNuGetPackagesAsync(string projectName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
await TestServices.Workspace.WaitForProjectSystemAsync(cancellationToken);
|
||||
|
||||
var solutionRestoreService = await GetComponentModelServiceAsync<IVsSolutionRestoreService>(cancellationToken);
|
||||
await solutionRestoreService.CurrentRestoreOperation;
|
||||
|
||||
var projectFullPath = (await GetProjectAsync(projectName, cancellationToken)).FullName;
|
||||
var solutionRestoreStatusProvider = await GetComponentModelServiceAsync<IVsSolutionRestoreStatusProvider>(cancellationToken);
|
||||
if (await solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var solutionRestoreService2 = (IVsSolutionRestoreService2)solutionRestoreService;
|
||||
await solutionRestoreService2.NominateProjectAsync(projectFullPath, cancellationToken);
|
||||
|
||||
// Check IsRestoreCompleteAsync until it returns true (this stops the retry because true != default(bool))
|
||||
await Helper.RetryAsync(
|
||||
cancellationToken => solutionRestoreStatusProvider.IsRestoreCompleteAsync(cancellationToken),
|
||||
TimeSpan.FromMilliseconds(50),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task OpenFileAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var filePath = await GetAbsolutePathForProjectRelativeFilePathAsync(projectName, relativeFilePath, cancellationToken);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException(filePath);
|
||||
}
|
||||
|
||||
VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, filePath, VSConstants.LOGVIEWID.Code_guid, out _, out _, out _, out var view);
|
||||
|
||||
// Reliably set focus using NavigateToLineAndColumn
|
||||
var textManager = await GetRequiredGlobalServiceAsync<SVsTextManager, IVsTextManager>(cancellationToken);
|
||||
ErrorHandler.ThrowOnFailure(view.GetBuffer(out var textLines));
|
||||
ErrorHandler.ThrowOnFailure(view.GetCaretPos(out var line, out var column));
|
||||
ErrorHandler.ThrowOnFailure(textManager.NavigateToLineAndColumn(textLines, VSConstants.LOGVIEWID.Code_guid, line, column, line, column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new file to project.
|
||||
/// </summary>
|
||||
/// <param name="projectName">The project that contains the file.</param>
|
||||
/// <param name="fileName">The name of the file to add.</param>
|
||||
/// <param name="contents">The contents of the file to overwrite. An empty file is create if null is passed.</param>
|
||||
/// <param name="open">Whether to open the file after it has been updated.</param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
public async Task AddFileAsync(string projectName, string fileName, string? contents = null, bool open = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var project = await GetProjectAsync(projectName, cancellationToken);
|
||||
var projectDirectory = Path.GetDirectoryName(project.FullName);
|
||||
var filePath = Path.Combine(projectDirectory, fileName);
|
||||
var directoryPath = Path.GetDirectoryName(filePath);
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
|
||||
if (contents is not null)
|
||||
{
|
||||
File.WriteAllText(filePath, contents);
|
||||
}
|
||||
else if (!File.Exists(filePath))
|
||||
{
|
||||
File.Create(filePath).Dispose();
|
||||
}
|
||||
|
||||
_ = project.ProjectItems.AddFromFile(filePath);
|
||||
|
||||
if (open)
|
||||
{
|
||||
await OpenFileAsync(projectName, fileName, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateSolutionAsync(string solutionPath, string solutionName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
await CloseSolutionAsync(cancellationToken);
|
||||
|
||||
var solutionFileName = Path.ChangeExtension(solutionName, ".sln");
|
||||
Directory.CreateDirectory(solutionPath);
|
||||
|
||||
// Make sure the shell debugger package is loaded so it doesn't try to load during the synchronous portion
|
||||
// of IVsSolution.CreateSolution.
|
||||
//
|
||||
// TODO: Identify the correct tracking bug
|
||||
_ = await GetRequiredGlobalServiceAsync<SVsShellDebugger, IVsDebugger>(cancellationToken);
|
||||
|
||||
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution>(cancellationToken);
|
||||
ErrorHandler.ThrowOnFailure(solution.CreateSolution(solutionPath, solutionFileName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT));
|
||||
ErrorHandler.ThrowOnFailure(solution.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0));
|
||||
}
|
||||
|
||||
private async Task<string> GetProjectTemplatePathAsync(string projectTemplate, string languageName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
var solution = (EnvDTE80.Solution2)dte.Solution;
|
||||
|
||||
if (string.Equals(languageName, "csharp", StringComparison.OrdinalIgnoreCase)
|
||||
&& GetCSharpProjectTemplates().TryGetValue(projectTemplate, out var csharpProjectTemplate))
|
||||
{
|
||||
return solution.GetProjectTemplate(csharpProjectTemplate, languageName);
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
|
||||
static ImmutableDictionary<string, string> GetCSharpProjectTemplates()
|
||||
{
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, string>();
|
||||
builder[WellKnownProjectTemplates.BlazorProject] = "BlazorTemplate";
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertLanguageName(string languageName)
|
||||
{
|
||||
return languageName switch
|
||||
{
|
||||
LanguageNames.CSharp => "CSharp",
|
||||
LanguageNames.Razor => "CSharp",
|
||||
_ => throw new ArgumentException($"'{languageName}' is not supported.", nameof(languageName)),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string> GetAbsolutePathForProjectRelativeFilePathAsync(string projectName, string relativeFilePath, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
var solution = dte.Solution;
|
||||
Assumes.Present(solution);
|
||||
|
||||
var project = solution.Projects.Cast<EnvDTE.Project>().FirstOrDefault(x => x.Name == projectName);
|
||||
if (project is null)
|
||||
{
|
||||
Assert.True(false, $"{projectName} doesn't exist, had {string.Join(",", solution.Projects.Cast<EnvDTE.Project>().Select(p => p.Name))}");
|
||||
throw new NotImplementedException("Prevent null fallthrough");
|
||||
}
|
||||
|
||||
Assert.NotNull(project);
|
||||
var projectPath = Path.GetDirectoryName(project.FullName);
|
||||
return Path.Combine(projectPath, relativeFilePath);
|
||||
}
|
||||
|
||||
private async Task<string> GetDirectoryNameAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var solution = await GetRequiredGlobalServiceAsync<SVsSolution, IVsSolution>(cancellationToken);
|
||||
ErrorHandler.ThrowOnFailure(solution.GetSolutionInfo(out _, out var solutionFileFullPath, out _));
|
||||
if (string.IsNullOrEmpty(solutionFileFullPath))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Path.GetDirectoryName(solutionFileFullPath);
|
||||
}
|
||||
|
||||
private static string CreateTemporaryPath()
|
||||
{
|
||||
return Path.Combine(Path.GetTempPath(), "razor-test", Path.GetRandomFileName());
|
||||
}
|
||||
|
||||
private async Task<EnvDTE.Project> GetProjectAsync(string nameOrFileName, CancellationToken cancellationToken)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
|
||||
var dte = await GetRequiredGlobalServiceAsync<SDTE, EnvDTE.DTE>(cancellationToken);
|
||||
var solution = (EnvDTE80.Solution2)dte.Solution;
|
||||
return solution.Projects.OfType<EnvDTE.Project>().First(
|
||||
project =>
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnUIThread();
|
||||
return string.Equals(project.FileName, nameOrFileName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(project.Name, nameOrFileName, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test.InProcess
|
||||
{
|
||||
public static class WellKnownCommandNames
|
||||
{
|
||||
public const string Build_BuildSolution = "Build.BuildSolution";
|
||||
|
||||
public const string View_ErrorList = "View.ErrorList";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
|
||||
namespace Microsoft.VisualStudio.Extensibility.Testing
|
||||
{
|
||||
internal partial class WorkspaceInProcess
|
||||
{
|
||||
public Task WaitForAsyncOperationsAsync(string featuresToWaitFor, CancellationToken cancellationToken)
|
||||
=> WaitForAsyncOperationsAsync(featuresToWaitFor, waitForWorkspaceFirst: true, cancellationToken);
|
||||
|
||||
public async Task WaitForAsyncOperationsAsync(string featuresToWaitFor, bool waitForWorkspaceFirst, CancellationToken cancellationToken)
|
||||
{
|
||||
if (waitForWorkspaceFirst || featuresToWaitFor == FeatureAttribute.Workspace)
|
||||
{
|
||||
await WaitForProjectSystemAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// TODO: This currently no-ops on the FeaturesToWaitFor portion
|
||||
// because we lack any system to wait on it with
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public class IntializeTestFileAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
public override void Before(MethodInfo methodUnderTest)
|
||||
{
|
||||
var typeName = methodUnderTest.ReflectedType.Name;
|
||||
if (typeof(RazorSemanticTokensTests).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
|
||||
{
|
||||
RazorSemanticTokensTests.FileName = $"Semantic/TestFiles/{typeName}/{methodUnderTest.Name}";
|
||||
}
|
||||
}
|
||||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
if (typeof(RazorSemanticTokensTests).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
|
||||
{
|
||||
RazorSemanticTokensTests.FileName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public static class LanguageNames
|
||||
{
|
||||
public const string Razor = "Razor";
|
||||
public const string CSharp = "CSharp";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Microsoft.VisualStudio.Razor.Integration.Test</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.AspNetCore.Razor.Test.Common\NullableAttributes.cs" Link="NullableAttributes.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.RazorExtension\Microsoft.VisualStudio.RazorExtension.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(MicrosoftVisualStudioEditorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.Xunit" Version="$(MicrosoftVisualStudioExtensibilityTestingXunitVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Testing.SourceGenerator" Version="$(MicrosoftVisualStudioExtensibilityTestingSourceGeneratorVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Language.Intellisense" Version="$(MicrosoftVisualStudioLanguageIntellisensePackageVersion)" />
|
||||
<PackageReference Include="NuGet.SolutionRestoreManager.Interop" Version="$(NuGetSolutionRestoreManagerInteropVersion)" />
|
||||
<PackageReference Include="Microsoft.Internal.VisualStudio.Shell.Framework" Version="$(MicrosoftInternalVisualStudioShellFrameworkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Semantic\TestFiles\**\*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public class ProjectTests : AbstractRazorEditorTest
|
||||
{
|
||||
[IdeFact]
|
||||
public async Task CreateFromTemplateAsync()
|
||||
{
|
||||
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
|
||||
await TestServices.SolutionExplorer.CloseSolutionAsync(HangMitigatingCancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public class RazorCodeActionsTests : AbstractRazorEditorTest
|
||||
{
|
||||
[IdeFact(Skip = "Behavior not yet testable")]
|
||||
public async Task RazorCodeActions_Show()
|
||||
{
|
||||
// Create Warnings by removing usings
|
||||
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, ImportsRazorFile, HangMitigatingCancellationToken);
|
||||
await TestServices.Editor.SetTextAsync("", HangMitigatingCancellationToken);
|
||||
|
||||
// Open the file
|
||||
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
|
||||
|
||||
await TestServices.Editor.SetTextAsync("<SurveyPrompt></SurveyPrompt>", HangMitigatingCancellationToken);
|
||||
await TestServices.Editor.MoveCaretAsync(3, HangMitigatingCancellationToken);
|
||||
|
||||
// Act
|
||||
var codeActions = await TestServices.Editor.InvokeCodeActionListAsync(HangMitigatingCancellationToken);
|
||||
|
||||
// Assert
|
||||
var codeActionSet = Assert.Single(codeActions);
|
||||
var usingString = $"@using {BlazorProjectName}.Shared";
|
||||
var codeAction = Assert.Single(codeActionSet.Actions, a => a.DisplayText.Equals(usingString));
|
||||
|
||||
await TestServices.Editor.InvokeCodeActionAsync(codeAction, HangMitigatingCancellationToken);
|
||||
|
||||
await TestServices.Editor.VerifyTextContainsAsync(usingString, HangMitigatingCancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
internal static class RazorDebug
|
||||
{
|
||||
/// <inheritdoc cref="Debug.Assert(bool)"/>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Assert([DoesNotReturnIf(false)] bool b) => Debug.Assert(b);
|
||||
|
||||
/// <inheritdoc cref="Debug.Assert(bool, string)"/>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Assert([DoesNotReturnIf(false)] bool b, string message)
|
||||
=> Debug.Assert(b, message);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void AssertNotNull<T>([NotNull] T value)
|
||||
{
|
||||
Assert(value is object, "Unexpected null reference");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generally <see cref="Debug.Assert(bool)"/> is a sufficient method for enforcing DEBUG
|
||||
/// only invariants in our code. When it triggers that provides a nice stack trace for
|
||||
/// investigation. Generally that is enough.
|
||||
///
|
||||
/// <para>There are cases for which a stack is not enough and we need a full heap dump to
|
||||
/// investigate the failure. This method takes care of that. The behavior is that when running
|
||||
/// in our CI environment if the assert triggers we will rudely crash the process and
|
||||
/// produce a heap dump for investigation.</para>
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
internal static void AssertOrFailFast([DoesNotReturnIf(false)] bool condition, string? message = null)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_DUMP_FOLDER")))
|
||||
{
|
||||
message ??= $"{nameof(AssertOrFailFast)} failed";
|
||||
var stackTrace = new StackTrace();
|
||||
Console.WriteLine(message);
|
||||
Console.WriteLine(stackTrace);
|
||||
|
||||
// Use FailFast so that the process fails rudely and goes through
|
||||
// windows error reporting (on Windows at least). This will allow our
|
||||
// Helix environment to capture crash dumps for future investigation
|
||||
Environment.FailFast(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(false, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.VisualStudio.Razor.Integration.Test.InProcess;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Classification;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
[IntializeTestFile]
|
||||
public class RazorSemanticTokensTests : AbstractRazorEditorTest
|
||||
{
|
||||
private static readonly AsyncLocal<string?> s_fileName = new();
|
||||
|
||||
private static readonly string s_projectPath = TestProject.GetProjectDirectory(typeof(RazorSemanticTokensTests), useCurrentDirectory: true);
|
||||
|
||||
protected bool GenerateBaselines { get; set; } = false;
|
||||
|
||||
// Used by the test framework to set the 'base' name for test files.
|
||||
public static string? FileName
|
||||
{
|
||||
get { return s_fileName.Value; }
|
||||
set { s_fileName.Value = value; }
|
||||
}
|
||||
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
await base.InitializeAsync();
|
||||
await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.Classification, HangMitigatingCancellationToken);
|
||||
}
|
||||
|
||||
[IdeFact]
|
||||
public async Task Components_AreColored()
|
||||
{
|
||||
// Arrange
|
||||
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, MainLayoutFile, HangMitigatingCancellationToken);
|
||||
|
||||
// Act
|
||||
await TestServices.Editor.WaitForClassificationAsync(HangMitigatingCancellationToken, "RazorComponentElement", 3);
|
||||
|
||||
// Assert
|
||||
var expectedClassifications = await GetExpectedClassificationSpansAsync(nameof(Components_AreColored), HangMitigatingCancellationToken);
|
||||
await TestServices.Editor.VerifyGetClassificationsAsync(expectedClassifications, HangMitigatingCancellationToken);
|
||||
}
|
||||
|
||||
[IdeFact]
|
||||
public async Task Directives_AreColored()
|
||||
{
|
||||
// Arrange
|
||||
await TestServices.SolutionExplorer.OpenFileAsync(BlazorProjectName, CounterRazorFile, HangMitigatingCancellationToken);
|
||||
await TestServices.Editor.WaitForClassificationAsync(HangMitigatingCancellationToken);
|
||||
|
||||
// Act and Assert
|
||||
var expectedClassifications = await GetExpectedClassificationSpansAsync(nameof(Directives_AreColored), HangMitigatingCancellationToken);
|
||||
await TestServices.Editor.VerifyGetClassificationsAsync(expectedClassifications, HangMitigatingCancellationToken);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ClassificationSpan>> GetExpectedClassificationSpansAsync(string testName, CancellationToken cancellationToken)
|
||||
{
|
||||
var snapshot = await TestServices.Editor.GetActiveSnapshotAsync(HangMitigatingCancellationToken);
|
||||
|
||||
if (GenerateBaselines)
|
||||
{
|
||||
var actual = await TestServices.Editor.GetClassificationsAsync(cancellationToken);
|
||||
GenerateSemanticBaseline(actual, testName);
|
||||
}
|
||||
|
||||
var expectedClassifications = await ReadSemanticBaselineAsync(snapshot, cancellationToken);
|
||||
|
||||
return expectedClassifications;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ClassificationSpan>> ReadSemanticBaselineAsync(ITextSnapshot snapshot, CancellationToken cancellationToken)
|
||||
{
|
||||
var baselinePath = Path.ChangeExtension(FileName, ".txt");
|
||||
var assembly = GetType().GetTypeInfo().Assembly;
|
||||
var semanticFile = TestFile.Create(baselinePath, assembly);
|
||||
|
||||
var semanticStr = await semanticFile.ReadAllTextAsync(cancellationToken);
|
||||
|
||||
return ParseSemanticBaseline(semanticStr, snapshot);
|
||||
|
||||
static IEnumerable<ClassificationSpan> ParseSemanticBaseline(string semanticStr, ITextSnapshot snapshot)
|
||||
{
|
||||
var result = new List<ClassificationSpan>();
|
||||
var strArray = semanticStr.Split(new[] { Separator.ToString(), Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (var i = 0; i < strArray.Length; i += 3)
|
||||
{
|
||||
if (!int.TryParse(strArray[i], out var position))
|
||||
{
|
||||
throw new InvalidOperationException($"{strArray[i]} was not an int {i}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(strArray[i + 1], out var length))
|
||||
{
|
||||
throw new InvalidOperationException($"{strArray[i + 1]} was not an int {i}");
|
||||
}
|
||||
|
||||
var snapshotSpan = new SnapshotSpan(snapshot, position, length);
|
||||
|
||||
var classification = strArray[i + 2];
|
||||
var classificationType = new ClassificationType(classification);
|
||||
|
||||
result.Add(new ClassificationSpan(snapshotSpan, classificationType));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private const char Separator = ',';
|
||||
|
||||
private static void GenerateSemanticBaseline(IEnumerable<ClassificationSpan> actual, string baselineFileName)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var baseline in actual)
|
||||
{
|
||||
builder.Append(baseline.Span.Start.Position).Append(Separator);
|
||||
builder.Append(baseline.Span.Length).Append(Separator);
|
||||
|
||||
var classification = baseline.ClassificationType;
|
||||
string? classificationStr = null;
|
||||
if (classification.BaseTypes.Count() > 1)
|
||||
{
|
||||
foreach (ILayeredClassificationType baseType in classification.BaseTypes)
|
||||
{
|
||||
if (baseType.Layer == ClassificationLayer.Semantic)
|
||||
{
|
||||
classificationStr = baseType.Classification;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (classificationStr is null)
|
||||
{
|
||||
Assert.True(false, "Tried to write layered classifications without Semantic layer");
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
classificationStr = classification.Classification;
|
||||
}
|
||||
|
||||
builder.Append(classificationStr).Append(Separator);
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
var semanticBaselinePath = GetBaselineFileName(baselineFileName);
|
||||
File.WriteAllText(semanticBaselinePath, builder.ToString());
|
||||
}
|
||||
|
||||
private static string GetBaselineFileName(string testName)
|
||||
{
|
||||
var semanticBaselinePath = Path.Combine(s_projectPath, "Semantic", "TestFiles", nameof(RazorSemanticTokensTests), testName + ".txt");
|
||||
return semanticBaselinePath;
|
||||
}
|
||||
|
||||
private class ClassificationComparer : IEqualityComparer<ClassificationSpan>
|
||||
{
|
||||
public static ClassificationComparer Instance = new();
|
||||
|
||||
public bool Equals(ClassificationSpan x, ClassificationSpan y)
|
||||
{
|
||||
var spanEquals = x.Span.Equals(y.Span);
|
||||
var classificationEquals = ClassificationTypeComparer.Instance.Equals(x.ClassificationType, y.ClassificationType);
|
||||
return classificationEquals && spanEquals;
|
||||
}
|
||||
|
||||
public int GetHashCode(ClassificationSpan obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassificationTypeComparer : IEqualityComparer<IClassificationType>
|
||||
{
|
||||
public static ClassificationTypeComparer Instance = new();
|
||||
|
||||
public bool Equals(IClassificationType x, IClassificationType y)
|
||||
{
|
||||
string xString;
|
||||
string yString;
|
||||
|
||||
if (x is ILayeredClassificationType xLayered)
|
||||
{
|
||||
var baseType = xLayered.BaseTypes.Single(b => b is ILayeredClassificationType bLayered && bLayered.Layer == ClassificationLayer.Semantic);
|
||||
xString = baseType.Classification;
|
||||
}
|
||||
else
|
||||
{
|
||||
xString = x.Classification;
|
||||
}
|
||||
|
||||
if (y is ILayeredClassificationType yLayered)
|
||||
{
|
||||
var baseType = yLayered.BaseTypes.Single(b => b is ILayeredClassificationType bLayered && bLayered.Layer == ClassificationLayer.Semantic);
|
||||
yString = baseType.Classification;
|
||||
}
|
||||
else
|
||||
{
|
||||
yString = y.Classification;
|
||||
}
|
||||
|
||||
return xString.Equals(yString);
|
||||
}
|
||||
|
||||
public int GetHashCode(IClassificationType obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassificationType : IClassificationType
|
||||
{
|
||||
public ClassificationType(string classification)
|
||||
{
|
||||
Classification = classification;
|
||||
}
|
||||
|
||||
public string Classification { get; }
|
||||
|
||||
public IEnumerable<IClassificationType> BaseTypes => throw new System.NotImplementedException();
|
||||
|
||||
public bool IsOfType(string type)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
0,1,RazorDirective,
|
||||
1,8,RazorDirective,
|
||||
10,19,class name,
|
||||
33,1,HTML Tag Delimiter,
|
||||
34,9,RazorComponentElement,
|
||||
43,1,HTML Tag Delimiter,
|
||||
57,1,HTML Tag Delimiter,
|
||||
58,1,HTML Tag Delimiter,
|
||||
59,9,RazorComponentElement,
|
||||
68,1,HTML Tag Delimiter,
|
||||
73,1,HTML Tag Delimiter,
|
||||
74,3,HTML Element Name,
|
||||
78,5,HTML Attribute Name,
|
||||
83,1,HTML Operator,
|
||||
84,1,HTML Attribute Value,
|
||||
85,4,HTML Attribute Value,
|
||||
89,1,HTML Attribute Value,
|
||||
90,1,HTML Tag Delimiter,
|
||||
97,1,HTML Tag Delimiter,
|
||||
98,3,HTML Element Name,
|
||||
102,5,HTML Attribute Name,
|
||||
107,1,HTML Operator,
|
||||
108,1,HTML Attribute Value,
|
||||
109,7,HTML Attribute Value,
|
||||
116,1,HTML Attribute Value,
|
||||
117,1,HTML Tag Delimiter,
|
||||
128,1,HTML Tag Delimiter,
|
||||
129,7,RazorComponentElement,
|
||||
136,1,HTML Tag Delimiter,
|
||||
137,1,HTML Tag Delimiter,
|
||||
138,1,HTML Tag Delimiter,
|
||||
145,1,HTML Tag Delimiter,
|
||||
146,1,HTML Tag Delimiter,
|
||||
147,3,HTML Element Name,
|
||||
150,1,HTML Tag Delimiter,
|
||||
159,1,HTML Tag Delimiter,
|
||||
160,4,HTML Element Name,
|
||||
164,1,HTML Tag Delimiter,
|
||||
175,1,HTML Tag Delimiter,
|
||||
176,3,HTML Element Name,
|
||||
180,5,HTML Attribute Name,
|
||||
185,1,HTML Operator,
|
||||
186,1,HTML Attribute Value,
|
||||
187,7,HTML Attribute Value,
|
||||
194,5,HTML Attribute Value,
|
||||
199,1,HTML Attribute Value,
|
||||
200,1,HTML Tag Delimiter,
|
||||
215,1,HTML Tag Delimiter,
|
||||
216,1,HTML Element Name,
|
||||
218,4,HTML Attribute Name,
|
||||
222,1,HTML Operator,
|
||||
223,1,HTML Attribute Value,
|
||||
224,34,HTML Attribute Value,
|
||||
258,1,HTML Attribute Value,
|
||||
260,6,HTML Attribute Name,
|
||||
266,1,HTML Operator,
|
||||
267,1,HTML Attribute Value,
|
||||
268,6,HTML Attribute Value,
|
||||
274,1,HTML Attribute Value,
|
||||
275,1,HTML Tag Delimiter,
|
||||
281,1,HTML Tag Delimiter,
|
||||
282,1,HTML Tag Delimiter,
|
||||
283,1,HTML Element Name,
|
||||
284,1,HTML Tag Delimiter,
|
||||
295,1,HTML Tag Delimiter,
|
||||
296,1,HTML Tag Delimiter,
|
||||
297,3,HTML Element Name,
|
||||
300,1,HTML Tag Delimiter,
|
||||
313,1,HTML Tag Delimiter,
|
||||
314,7,HTML Element Name,
|
||||
322,5,HTML Attribute Name,
|
||||
327,1,HTML Operator,
|
||||
328,1,HTML Attribute Value,
|
||||
329,7,HTML Attribute Value,
|
||||
336,5,HTML Attribute Value,
|
||||
341,1,HTML Attribute Value,
|
||||
342,1,HTML Tag Delimiter,
|
||||
357,1,RazorDirective,
|
||||
358,4,property name,
|
||||
372,1,HTML Tag Delimiter,
|
||||
373,1,HTML Tag Delimiter,
|
||||
374,7,HTML Element Name,
|
||||
381,1,HTML Tag Delimiter,
|
||||
388,1,HTML Tag Delimiter,
|
||||
389,1,HTML Tag Delimiter,
|
||||
390,4,HTML Element Name,
|
||||
394,1,HTML Tag Delimiter,
|
||||
397,1,HTML Tag Delimiter,
|
||||
398,1,HTML Tag Delimiter,
|
||||
399,3,HTML Element Name,
|
||||
402,1,HTML Tag Delimiter,
|
|
@ -0,0 +1,70 @@
|
|||
0,1,RazorDirective,
|
||||
1,4,RazorDirective,
|
||||
6,10,string,
|
||||
20,1,HTML Tag Delimiter,
|
||||
21,9,RazorComponentElement,
|
||||
30,1,HTML Tag Delimiter,
|
||||
38,1,HTML Tag Delimiter,
|
||||
39,1,HTML Tag Delimiter,
|
||||
40,9,RazorComponentElement,
|
||||
49,1,HTML Tag Delimiter,
|
||||
54,1,HTML Tag Delimiter,
|
||||
55,2,HTML Element Name,
|
||||
57,1,HTML Tag Delimiter,
|
||||
65,1,HTML Tag Delimiter,
|
||||
66,1,HTML Tag Delimiter,
|
||||
67,2,HTML Element Name,
|
||||
69,1,HTML Tag Delimiter,
|
||||
74,1,HTML Tag Delimiter,
|
||||
75,1,HTML Element Name,
|
||||
77,4,HTML Attribute Name,
|
||||
81,1,HTML Operator,
|
||||
82,1,HTML Attribute Value,
|
||||
83,6,HTML Attribute Value,
|
||||
89,1,HTML Attribute Value,
|
||||
90,1,HTML Tag Delimiter,
|
||||
106,1,RazorDirective,
|
||||
107,12,field name,
|
||||
119,1,HTML Tag Delimiter,
|
||||
120,1,HTML Tag Delimiter,
|
||||
121,1,HTML Element Name,
|
||||
122,1,HTML Tag Delimiter,
|
||||
127,1,HTML Tag Delimiter,
|
||||
128,6,HTML Element Name,
|
||||
135,5,HTML Attribute Name,
|
||||
140,1,HTML Operator,
|
||||
141,1,HTML Attribute Value,
|
||||
142,3,HTML Attribute Value,
|
||||
145,12,HTML Attribute Value,
|
||||
157,1,HTML Attribute Value,
|
||||
159,1,RazorDirective,
|
||||
160,7,RazorDirectiveAttribute,
|
||||
167,1,HTML Operator,
|
||||
168,1,HTML Attribute Value,
|
||||
169,14,method name,
|
||||
183,1,HTML Attribute Value,
|
||||
184,1,HTML Tag Delimiter,
|
||||
193,1,HTML Tag Delimiter,
|
||||
194,1,HTML Tag Delimiter,
|
||||
195,6,HTML Element Name,
|
||||
201,1,HTML Tag Delimiter,
|
||||
206,1,RazorDirective,
|
||||
207,4,RazorDirective,
|
||||
212,1,RazorDirective,
|
||||
219,7,keyword,
|
||||
227,3,keyword,
|
||||
231,12,field name,
|
||||
244,1,operator,
|
||||
246,1,literal,
|
||||
247,1,punctuation,
|
||||
256,7,keyword,
|
||||
264,4,keyword,
|
||||
269,14,method name,
|
||||
283,1,punctuation,
|
||||
284,1,punctuation,
|
||||
291,1,punctuation,
|
||||
302,12,field name,
|
||||
314,2,operator,
|
||||
316,1,punctuation,
|
||||
323,1,punctuation,
|
||||
326,1,RazorDirective,
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Razor.Integration.Test
|
||||
{
|
||||
public static class WellKnownProjectTemplates
|
||||
{
|
||||
public const string BlazorProject = "Microsoft.WAP.CSharp.ASPNET.Blazor";
|
||||
|
||||
public static class GroupIdentifiers
|
||||
{
|
||||
public const string Server = "Microsoft.Web.Blazor.Server";
|
||||
public const string Wasm = "Microsoft.Web.Blazor.Wasm";
|
||||
}
|
||||
|
||||
public static class TemplateIdentifiers
|
||||
{
|
||||
public const string Server31 = "Microsoft.Web.Blazor.Server.CSharp.3.1";
|
||||
public const string Server50 = "Microsoft.Web.Blazor.Server.CSharp.5.0";
|
||||
public const string Server60 = "Microsoft.Web.Blazor.Server.CSharp.6.0";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"methodDisplay": "method",
|
||||
"shadowCopy": false
|
||||
}
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
<p>Words!</p>
|
||||
|
||||
<Counter />
|
||||
<NavMenu />
|
||||
|
|
Загрузка…
Ссылка в новой задаче