File: Formatting\FormatDocumentTests.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.VisualStudio.Razor.IntegrationTests\Microsoft.VisualStudio.Razor.IntegrationTests.csproj (Microsoft.VisualStudio.Razor.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.VisualStudio.Razor.IntegrationTests;
 
public class FormatDocumentTests(ITestOutputHelper testOutputHelper) : AbstractRazorEditorTest(testOutputHelper)
{
    private static string? s_projectPath;
 
    // To add new formatting tests create a sample file of the "before" state
    // and place it in the TestFiles\Input folder.
    // If you know the "after" state then you can place that in a file of the same
    // name in the TestFiles\Expected folder, and run tests as normal.
    // If you want to generate the "after" state simple run the test without
    // creating the expected file, and it will be generated for you.
    //
    // NOTE: Formatting must change the input state or the test will hang
    // ie. these tests cannot be used for pure validation
 
    [IdeTheory]
    [MemberData(nameof(GetFormattingTestResourceNames))]
    public async Task FormattingDocument(string testFileName)
    {
        var inputResourceName = GetResourceName(testFileName, "Input");
        var expectedResourceName = GetResourceName(testFileName, "Expected");
 
        if (!TryGetResource(inputResourceName, out var input))
        {
            throw new Exception($"Could not get input resource data for '{inputResourceName}'");
        }
 
        // Open the file
        if (testFileName.EndsWith(".razor", StringComparison.OrdinalIgnoreCase))
        {
            await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.CounterRazorFile, ControlledHangMitigatingCancellationToken);
        }
        else
        {
            await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.ErrorCshtmlFile, ControlledHangMitigatingCancellationToken);
        }
 
        await TestServices.Editor.SetTextAsync(input, ControlledHangMitigatingCancellationToken);
 
        // Wait for the document to settle
        await TestServices.Editor.WaitForSemanticClassificationAsync("RazorTransition", ControlledHangMitigatingCancellationToken);
 
        // Act
        await TestServices.Editor.InvokeFormatDocumentAsync(ControlledHangMitigatingCancellationToken);
 
        // Assert
        var actual = await TestServices.Editor.WaitForTextChangeAsync(input, ControlledHangMitigatingCancellationToken);
 
        if (!TryGetResource(expectedResourceName, out var expected))
        {
            // If there was no expected results file, we generate one, but still fail
            // the test so that its impossible to forget to commit the results.
            s_projectPath ??= TestProject.GetProjectDirectory(typeof(FormatDocumentTests), layer: TestProject.Layer.Tooling, useCurrentDirectory: true);
            var path = Path.Combine(s_projectPath, "Formatting", "TestFiles", "Expected");
            var fileName = expectedResourceName.Split(new[] { '.' }, 8).Last();
 
            File.WriteAllText(Path.Combine(path, fileName), actual);
 
            throw new Exception("Test did not have expected results file so one has been generated. Running the test again should make it pass.");
        }
 
        AssertEx.EqualOrDiff(expected, actual);
    }
 
    private static string GetResourceBaseName()
        => $"{typeof(FormatDocumentTests).Namespace}.Formatting.TestFiles";
 
    private static string GetResourceName(string name, string suffix)
        => $"{GetResourceBaseName()}.{suffix}.{name}";
 
    private static bool TryGetResource(string name, [NotNullWhen(true)] out string? value)
    {
        try
        {
            using var expectedStream = typeof(FormatDocumentTests).Assembly.GetManifestResourceStream(name);
            using var sr = new StreamReader(expectedStream);
 
            value = sr.ReadToEnd();
        }
        catch
        {
            value = null;
            return false;
        }
 
        return true;
    }
 
    public static TheoryData<string> GetFormattingTestResourceNames()
    {
        var baseName = $"{GetResourceBaseName()}.Input.";
        var data = new TheoryData<string>();
        var names = typeof(FormatDocumentTests).Assembly.GetManifestResourceNames();
 
        foreach (var name in names)
        {
            if (name.StartsWith(baseName))
            {
                data.Add(Path.GetFileName(name)[baseName.Length..]);
            }
        }
 
        return data;
    }
}