File: WasmJsModulesIntegrationTests.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Sdk.BlazorWebAssembly.Tests\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj (Microsoft.NET.Sdk.BlazorWebAssembly.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Text.Json;
using Microsoft.AspNetCore.StaticWebAssets.Tasks;
using Microsoft.NET.Sdk.WebAssembly;
 
namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tests
{
    public class WasmJsModulesIntegrationTests(ITestOutputHelper log) : BlazorWasmBaselineTests(log, GenerateBaselines)
    {
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest()
        {
            // Arrange
            var testAsset = "BlazorWasmMinimal";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
 
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "wwwroot", "blazorwasm-minimal.lib.module.js"), "console.log('Hello initializer')");
 
            var build = CreateBuildCommand(ProjectDirectory);
            var buildResult = ExecuteCommand(build);
            buildResult.Should().Pass();
 
            var outputPath = build.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.build.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadBuildManifest());
 
            var blazorBootJson = new FileInfo(Path.Combine(intermediateOutputPath, WasmBootConfigFileName));
            blazorBootJson.Should().Exist();
            var contents = JsonSerializer.Deserialize<JsonDocument>(BootJsonDataLoader.GetJsonContent(blazorBootJson.FullName));
            contents.RootElement.TryGetProperty("resources", out var resources).Should().BeTrue();
            resources.TryGetProperty("libraryInitializers", out var initializers).Should().BeTrue();
            initializers.ValueKind.Should().Be(JsonValueKind.Array);
            initializers
                .EnumerateArray()
                .Single(i => i.TryGetProperty("name", out var name) && name.GetString() == "blazorwasm-minimal.lib.module.js");
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.modules.json")).Should().NotExist();
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void JSModules_ManifestIncludesModuleTargetPaths()
        {
            // Arrange
            var testAsset = "BlazorHosted";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    if (Path.GetFileName(p) == "blazorwasm.csproj")
                    {
                        var itemGroup = new XElement("PropertyGroup");
                        var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                        itemGroup.Add(fingerprintAssets);
                        doc.Root.Add(itemGroup);
                    }
                });
 
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "blazorwasm", "wwwroot", "blazorwasm.lib.module.js"), "console.log('Hello initializer')");
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "razorclasslibrary", "wwwroot", "razorclasslibrary.lib.module.js"), "console.log('Hello RCL initializer')");
 
            var build = CreateBuildCommand(ProjectDirectory, "blazorhosted");
            var buildResult = ExecuteCommand(build);
            buildResult.Should().Pass();
 
            var outputPath = build.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = build.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.build.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadBuildManifest());
 
            var blazorBootJson = new FileInfo(Path.Combine(intermediateOutputPath.Replace("blazorhosted", "blazorwasm"), WasmBootConfigFileName));
            blazorBootJson.Should().Exist();
            var contents = JsonSerializer.Deserialize<JsonDocument>(BootJsonDataLoader.GetJsonContent(blazorBootJson.FullName));
            contents.RootElement.TryGetProperty("resources", out var resources).Should().BeTrue();
            resources.TryGetProperty("libraryInitializers", out var initializers).Should().BeTrue();
            initializers.ValueKind.Should().Be(JsonValueKind.Array);
            initializers
                .EnumerateArray()
                .Single(i => i.TryGetProperty("name", out var name) && name.GetString() == "blazorwasm.lib.module.js");
            initializers
                .EnumerateArray()
                .Single(i => i.TryGetProperty("name", out var name) && name.GetString() == "_content/RazorClassLibrary/razorclasslibrary.lib.module.js");
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorhosted.modules.json")).Should().NotExist();
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Publish_DoesNotGenerateManifestJson_IncludesJSModulesOnBlazorBootJsonManifest()
        {
            // Arrange
            var testAsset = "BlazorWasmMinimal";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
 
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "wwwroot", "blazorwasm-minimal.lib.module.js"), "console.log('Hello initializer')");
 
            var publish = CreatePublishCommand(ProjectDirectory);
            var publishResult = ExecuteCommand(publish);
            publishResult.Should().Pass();
 
            var outputPath = publish.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = publish.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.publish.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadPublishManifest());
 
            var blazorBootJson = new FileInfo(Path.Combine(intermediateOutputPath, $"publish.{WasmBootConfigFileName}"));
            blazorBootJson.Should().Exist();
            var contents = JsonSerializer.Deserialize<JsonDocument>(BootJsonDataLoader.GetJsonContent(blazorBootJson.FullName));
            contents.RootElement.TryGetProperty("resources", out var resources).Should().BeTrue();
            resources.TryGetProperty("libraryInitializers", out var initializers).Should().BeTrue();
            initializers.ValueKind.Should().Be(JsonValueKind.Array);
            initializers
                .EnumerateArray()
                .Single(i => i.TryGetProperty("name", out var name) && name.GetString() == "blazorwasm-minimal.lib.module.js");
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.modules.json")).Should().NotExist();
 
            var lib = new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.lib.module.js"));
            lib.Should().Exist();
 
            AssertPublishAssets(
                manifest,
                outputPath,
                intermediateOutputPath);
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void JsModules_CanHaveDifferentBuildAndPublishModules()
        {
            // Arrange
            var testAsset = "BlazorWasmMinimal";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset)
                .WithProjectChanges((p, doc) =>
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    doc.Root.Add(itemGroup);
                });
 
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "wwwroot", "blazorwasm-minimal.lib.module.js"), "console.log('Publish initializer')");
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "wwwroot", "blazorwasm-minimal.lib.module.build.js"), "console.log('Build initializer')");
 
            ProjectDirectory.WithProjectChanges(document =>
            {
                var itemGroup = new XElement("PropertyGroup");
                var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                itemGroup.Add(fingerprintAssets);
                document.Root.Add(itemGroup);
 
                document.Root.Add(new XElement("ItemGroup",
                    new XElement("Content",
                        new XAttribute("Update", "wwwroot\\blazorwasm-minimal.lib.module.build.js"),
                        new XAttribute("CopyToPublishDirectory", "Never"),
                        new XAttribute("TargetPath", "wwwroot\\blazorwasm-minimal.lib.module.js"))));
            });
 
            var publish = CreatePublishCommand(ProjectDirectory);
            var publishResult = ExecuteCommand(publish);
            publishResult.Should().Pass();
 
            var outputPath = publish.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = publish.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.publish.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadPublishManifest());
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.modules.json")).Should().NotExist();
            var lib = new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.lib.module.js"));
            lib.Should().Exist();
 
            AssertPublishAssets(
                manifest,
                outputPath,
                intermediateOutputPath);
        }
 
        [Fact]
        public void JsModules_CanCustomizeBlazorInitialization()
        {
            // Arrange
            var testAsset = "BlazorWasmMinimal";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset);
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "wwwroot", "blazorwasm-minimal.lib.module.js"), "console.log('Hello initializer')");
 
            ProjectDirectory.WithProjectChanges(document =>
            {
                document.Root.Add(new XElement("PropertyGroup",
                    new XElement("WasmFingerprintAssets", false)));
                document.Root.Add(
                    XElement.Parse(@"
<PropertyGroup>
  <ComputeBlazorExtensionsDependsOn>$(ComputeBlazorExtensionsDependsOn);_CustomizeBlazorBootProcess</ComputeBlazorExtensionsDependsOn>
</PropertyGroup>"),
                    XElement.Parse(@"
<Target Name=""_CustomizeBlazorBootProcess"">
  <ItemGroup>
  <BlazorPublishExtension Include=""$(IntermediateOutputPath)publish.extension.txt"">
    <ExtensionName>my-custom-extension</ExtensionName>
    <RelativePath>_bin/publish.extension.txt</RelativePath>
  </BlazorPublishExtension>
  <FileWrites Include=""$(IntermediateOutputPath)publish.extension.txt"" />
  </ItemGroup>
 
  <WriteLinesToFile
    Lines=""@(_BlazorBootFilesToUpdate->'%(FullPath)')""
    File=""$(IntermediateOutputPath)publish.extension.txt""
    WriteOnlyWhenDifferent=""true"" />
</Target>"));
            });
 
            var publish = CreatePublishCommand(ProjectDirectory);
            var publishResult = ExecuteCommand(publish);
            publishResult.Should().Pass();
 
            var outputPath = publish.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = publish.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.publish.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadPublishManifest());
 
            var publishExtension = GetPublishExtension(Path.Combine(intermediateOutputPath, $"publish.{WasmBootConfigFileName}"));
            GetPublishExtensionEntriesCount(Path.Combine(intermediateOutputPath, $"publish.{WasmBootConfigFileName}")).Should().Be(1);
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.modules.json")).Should().NotExist();
            var lib = new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm-minimal.lib.module.js"));
            lib.Should().Exist();
 
            var wwwrootPublishExtension = GetPublishExtension(Path.Combine(outputPath, "wwwroot", "_framework", WasmBootConfigFileName));
            publishExtension.GetString().Should().Be(wwwrootPublishExtension.GetString());
 
            var extension = new FileInfo(Path.Combine(outputPath, "wwwroot", "_bin", "publish.extension.txt"));
            extension.Should().Exist();
 
            AssertPublishAssets(
                manifest,
                outputPath,
                intermediateOutputPath);
        }
 
        [Fact]
        public void JsModules_Hosted_CanCustomizeBlazorInitialization()
        {
            // Arrange
            var testAsset = "BlazorHosted";
            ProjectDirectory = CreateAspNetSdkTestAsset(testAsset);
            File.WriteAllText(Path.Combine(ProjectDirectory.TestRoot, "blazorwasm", "wwwroot", "blazorwasm.lib.module.js"), "console.log('Hello initializer')");
 
            ProjectDirectory.WithProjectChanges((path, document) =>
            {
                if (Path.GetFileNameWithoutExtension(path) == "blazorwasm")
                {
                    var itemGroup = new XElement("PropertyGroup");
                    var fingerprintAssets = new XElement("WasmFingerprintAssets", false);
                    itemGroup.Add(fingerprintAssets);
                    document.Root.Add(itemGroup);
 
                    document.Root.Add(
                        XElement.Parse(@"
<PropertyGroup>
  <ComputeBlazorExtensionsDependsOn>$(ComputeBlazorExtensionsDependsOn);_CustomizeBlazorBootProcess</ComputeBlazorExtensionsDependsOn>
</PropertyGroup>"),
                    XElement.Parse(@"
<Target Name=""_CustomizeBlazorBootProcess"">
  <ItemGroup>
  <BlazorPublishExtension Include=""$(IntermediateOutputPath)publish.extension.txt"">
    <ExtensionName>my-custom-extension</ExtensionName>
    <RelativePath>_bin/publish.extension.txt</RelativePath>
  </BlazorPublishExtension>
  <FileWrites Include=""$(IntermediateOutputPath)publish.extension.txt"" />
  </ItemGroup>
 
  <WriteLinesToFile
    Lines=""@(_BlazorBootFilesToUpdate->'%(FullPath)')""
    File=""$(IntermediateOutputPath)publish.extension.txt""
    WriteOnlyWhenDifferent=""true"" />
</Target>"));
                }
            });
 
            var publish = CreatePublishCommand(ProjectDirectory, "blazorhosted");
            var publishResult = ExecuteCommand(publish);
            publishResult.Should().Pass();
 
            var outputPath = publish.GetOutputDirectory(DefaultTfm).ToString();
            var intermediateOutputPath = publish.GetIntermediateDirectory(DefaultTfm, "Debug").ToString();
 
            var path = Path.Combine(intermediateOutputPath, "staticwebassets.publish.json");
            new FileInfo(path).Should().Exist();
            var manifest = StaticWebAssetsManifest.FromJsonBytes(File.ReadAllBytes(path));
            AssertManifest(manifest, LoadPublishManifest());
 
            var publishExtension = GetPublishExtension(Path.Combine(intermediateOutputPath.Replace("blazorhosted", "blazorwasm"), $"publish.{WasmBootConfigFileName}"));
 
            new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorhosted.modules.json")).Should().Exist();
            var lib = new FileInfo(Path.Combine(outputPath, "wwwroot", "blazorwasm.lib.module.js"));
            lib.Should().Exist();
 
            var wwwrootPublishExtension = GetPublishExtension(Path.Combine(outputPath, "wwwroot", "_framework", WasmBootConfigFileName));
            publishExtension.GetString().Should().Be(wwwrootPublishExtension.GetString());
 
            var extension = new FileInfo(Path.Combine(outputPath, "wwwroot", "_bin", "publish.extension.txt"));
            extension.Should().Exist();
 
            AssertPublishAssets(
                manifest,
                outputPath,
                intermediateOutputPath);
        }
 
        private static JsonElement GetPublishExtension(string path)
        {
            var blazorBootJson = new FileInfo(path);
            blazorBootJson.Should().Exist();
            var contents = JsonSerializer.Deserialize<JsonDocument>(BootJsonDataLoader.GetJsonContent(path));
            contents.RootElement.TryGetProperty("resources", out var resources).Should().BeTrue();
            resources.TryGetProperty("extensions", out var extensions).Should().BeTrue();
            extensions.TryGetProperty("my-custom-extension", out var extension).Should().BeTrue();
            extension.TryGetProperty("_bin/publish.extension.txt", out var file).Should().BeTrue();
            return file;
        }
 
        private static int GetPublishExtensionEntriesCount(string path)
        {
            var blazorBootJson = new FileInfo(path);
            blazorBootJson.Should().Exist();
            var contents = JsonSerializer.Deserialize<JsonDocument>(BootJsonDataLoader.GetJsonContent(path));
            contents.RootElement.TryGetProperty("resources", out var resources).Should().BeTrue();
            resources.TryGetProperty("extensions", out var extensions).Should().BeTrue();
            extensions.TryGetProperty("my-custom-extension", out var extension).Should().BeTrue();
            return extension.EnumerateObject().Count();
        }
    }
}