File: WasmPwaManifestTests.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 System.Text.RegularExpressions;
using static Microsoft.NET.Sdk.BlazorWebAssembly.Tests.ServiceWorkerAssert;
 
 
namespace Microsoft.NET.Sdk.BlazorWebAssembly.Tests
{
    public class WasmPwaManifestTests(ITestOutputHelper log) : AspNetSdkTest(log)
    {
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_ServiceWorkerAssetsManifest_Works()
        {
            // Arrange
            var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
            var testAppName = "BlazorWasmWithLibrary";
            var testInstance = CreateAspNetSdkTestAsset(testAppName)
                .WithProjectChanges((p, doc) =>
                {
                    if (Path.GetFileName(p) == "blazorwasm.csproj")
                    {
                        var itemGroup = new XElement("PropertyGroup");
                        var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
                        itemGroup.Add(new XElement("WasmFingerprintAssets", false));
                        itemGroup.Add(serviceWorkerAssetsManifest);
                        doc.Root.Add(itemGroup);
                    }
                });
 
            var buildCommand = CreateBuildCommand(testInstance, "blazorwasm");
            buildCommand.WithWorkingDirectory(testInstance.TestRoot);
            ExecuteCommand(buildCommand)
                .Should().Pass();
 
            var buildOutputDirectory = buildCommand.GetOutputDirectory(DefaultTfm).ToString();
 
            new FileInfo(Path.Combine(buildOutputDirectory, "wwwroot", "_framework", WasmBootConfigFileName)).Should().Exist();
            new FileInfo(Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "dotnet.native.wasm")).Should().Exist();
            new FileInfo(Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazorwasm.wasm")).Should().Exist();
 
            var serviceWorkerAssetsManifest = Path.Combine(buildOutputDirectory, "wwwroot", "service-worker-assets.js");
            // Trim prefix 'self.assetsManifest = ' and suffix ';'
            var manifestContents = File.ReadAllText(serviceWorkerAssetsManifest).TrimEnd()[22..^1];
 
            var manifestContentsJson = JsonDocument.Parse(manifestContents);
            manifestContentsJson.RootElement.TryGetProperty("assets", out var assets).Should().BeTrue();
            assets.ValueKind.Should().Be(JsonValueKind.Array);
 
            var entries = assets.EnumerateArray().Select(e => e.GetProperty("url").GetString()).OrderBy(e => e).ToArray();
            entries.Should().Contain(e => expectedExtensions.Contains(Path.GetExtension(e)));
 
            VerifyServiceWorkerFiles(testInstance,
               Path.Combine(buildOutputDirectory, "wwwroot"),
               serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"),
               serviceWorkerContent: "// This is the development service worker",
               assetsManifestPath: "service-worker-assets.js");
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Build_HostedAppWithServiceWorker_Works()
        {
            // Arrange
            var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
            var testAppName = "BlazorHosted";
            var testInstance = CreateAspNetSdkTestAsset(testAppName);
 
            var buildCommand = CreateBuildCommand(testInstance, "blazorhosted");
            ExecuteCommand(buildCommand)
                .Should().Pass();
 
            var buildOutputDirectory = OutputPathCalculator.FromProject(Path.Combine(testInstance.TestRoot, "blazorwasm")).GetOutputDirectory();
 
            var serviceWorkerAssetsManifest = Path.Combine(buildOutputDirectory, "wwwroot", "custom-service-worker-assets.js");
            // Trim prefix 'self.assetsManifest = ' and suffix ';'
            var manifestContents = File.ReadAllText(serviceWorkerAssetsManifest).TrimEnd()[22..^1];
 
            var manifestContentsJson = JsonDocument.Parse(manifestContents);
            manifestContentsJson.RootElement.TryGetProperty("assets", out var assets).Should().BeTrue();
            assets.ValueKind.Should().Be(JsonValueKind.Array);
 
            var entries = assets.EnumerateArray().Select(e => e.GetProperty("url").GetString()).OrderBy(e => e).ToArray();
            entries.Should().Contain(e => expectedExtensions.Contains(Path.GetExtension(e)));
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void PublishWithPWA_ProducesAssets()
        {
            // Arrange
            var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
            var testAppName = "BlazorWasmWithLibrary";
            var testInstance = CreateAspNetSdkTestAsset(testAppName);
 
            var publishCommand = CreatePublishCommand(testInstance, "blazorwasm");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();
 
            var serviceWorkerAssetsManifest = Path.Combine(publishOutputDirectory, "wwwroot", "custom-service-worker-assets.js");
            // Trim prefix 'self.assetsManifest = ' and suffix ';'
            var manifestContents = File.ReadAllText(serviceWorkerAssetsManifest).TrimEnd()[22..^1];
 
            var manifestContentsJson = JsonDocument.Parse(manifestContents);
            manifestContentsJson.RootElement.TryGetProperty("assets", out var assets).Should().BeTrue();
            Assert.Equal(JsonValueKind.Array, assets.ValueKind);
 
            var entries = assets.EnumerateArray().Select(e => e.GetProperty("url").GetString()).OrderBy(e => e).ToArray();
            entries.Should().Contain(e => expectedExtensions.Contains(Path.GetExtension(e)));
 
            var serviceWorkerFile = Path.Combine(publishOutputDirectory, "wwwroot", "serviceworkers", "my-service-worker.js");
            // Assert.FileContainsLine(result, serviceWorkerFile, "// This is the production service worker");
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void PublishHostedWithPWA_ProducesAssets()
        {
            // Arrange
            var expectedExtensions = new[] { ".pdb", ".js", ".wasm" };
            var testAppName = "BlazorHosted";
            var testInstance = CreateAspNetSdkTestAsset(testAppName);
 
            var publishCommand = CreatePublishCommand(testInstance, "blazorhosted");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();
 
            var serviceWorkerAssetsManifest = Path.Combine(publishOutputDirectory, "wwwroot", "custom-service-worker-assets.js");
            // Trim prefix 'self.assetsManifest = ' and suffix ';'
            var manifestContents = File.ReadAllText(serviceWorkerAssetsManifest).TrimEnd()[22..^1];
 
            var manifestContentsJson = JsonDocument.Parse(manifestContents);
            manifestContentsJson.RootElement.TryGetProperty("assets", out var assets).Should().BeTrue();
            assets.ValueKind.Should().Be(JsonValueKind.Array);
 
            var entries = assets.EnumerateArray().Select(e => e.GetProperty("url").GetString()).OrderBy(e => e).ToArray();
            entries.Should().Contain(e => expectedExtensions.Contains(Path.GetExtension(e)));
 
            var serviceWorkerFile = Path.Combine(publishOutputDirectory, "wwwroot", "serviceworkers", "my-service-worker.js");
            // Assert.FileContainsLine(result, serviceWorkerFile, "// This is the production service worker");
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Publish_UpdatesServiceWorkerVersionHash_WhenSourcesChange()
        {
            // Arrange
            var testAppName = "BlazorWasmWithLibrary";
            var testInstance = CreateAspNetSdkTestAsset(testAppName)
                .WithProjectChanges((p, doc) =>
                {
                    if (Path.GetFileName(p) == "blazorwasm.csproj")
                    {
                        var itemGroup = new XElement("PropertyGroup");
                        var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
                        itemGroup.Add(serviceWorkerAssetsManifest);
                        doc.Root.Add(itemGroup);
                    }
                });
 
            var publishCommand = CreatePublishCommand(testInstance, "blazorwasm");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();
 
            var serviceWorkerFile = Path.Combine(publishOutputDirectory, "wwwroot", "serviceworkers", "my-service-worker.js");
            var version = File.ReadAllLines(serviceWorkerFile).First();
            var match = Regex.Match(version, "\\/\\* Manifest version: (.{8}) \\*\\/");
            match.Success.Should().BeTrue();
            match.Groups.Count.Should().Be(2);
            match.Groups[1].Value.Should().NotBeNull();
 
            var capture = match.Groups[1].Value;
 
            // Act
            var cssFile = Path.Combine(testInstance.TestRoot, "blazorwasm", "LinkToWebRoot", "css", "app.css");
            File.WriteAllText(cssFile, ".updated { }");
 
            // Assert
            publishCommand = CreatePublishCommand(testInstance, "blazorwasm");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var updatedVersion = File.ReadAllLines(serviceWorkerFile).First();
            var updatedMatch = Regex.Match(updatedVersion, "\\/\\* Manifest version: (.{8}) \\*\\/");
 
            updatedMatch.Success.Should().BeTrue();
            updatedMatch.Groups.Count.Should().Be(2);
            updatedMatch.Groups[1].Value.Should().NotBeNull();
 
            var updatedCapture = updatedMatch.Groups[1].Value;
            updatedCapture.Should().NotBe(capture);
        }
 
        [RequiresMSBuildVersionFact("17.12", Reason = "Needs System.Text.Json 8.0.5")]
        public void Publish_DeterministicAcrossBuilds_WhenNoSourcesChange()
        {
            // Arrange
            var testAppName = "BlazorWasmWithLibrary";
            var testInstance = CreateAspNetSdkTestAsset(testAppName)
                .WithProjectChanges((p, doc) =>
                {
                    if (Path.GetFileName(p) == "blazorwasm.csproj")
                    {
                        var itemGroup = new XElement("PropertyGroup");
                        var serviceWorkerAssetsManifest = new XElement("ServiceWorkerAssetsManifest", "service-worker-assets.js");
                        itemGroup.Add(serviceWorkerAssetsManifest);
                        doc.Root.Add(itemGroup);
                    }
                });
 
            var publishCommand = CreatePublishCommand(testInstance, "blazorwasm");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var publishOutputDirectory = publishCommand.GetOutputDirectory(DefaultTfm).ToString();
 
            var serviceWorkerFile = Path.Combine(publishOutputDirectory, "wwwroot", "serviceworkers", "my-service-worker.js");
            var version = File.ReadAllLines(serviceWorkerFile).First();
            var match = Regex.Match(version, "\\/\\* Manifest version: (.{8}) \\*\\/");
            match.Success.Should().BeTrue();
            match.Groups.Count.Should().Be(2);
            match.Groups[1].Value.Should().NotBeNull();
 
            var capture = match.Groups[1].Value;
 
            // Act && Assert
            publishCommand = CreatePublishCommand(testInstance, "blazorwasm");
            ExecuteCommand(publishCommand).Should().Pass();
 
            var updatedVersion = File.ReadAllLines(serviceWorkerFile).First();
            var updatedMatch = Regex.Match(updatedVersion, "\\/\\* Manifest version: (.{8}) \\*\\/");
 
            updatedMatch.Success.Should().BeTrue();
            updatedMatch.Groups.Count.Should().Be(2);
            updatedMatch.Groups[1].Value.Should().NotBeNull();
 
            var updatedCapture = updatedMatch.Groups[1].Value;
            updatedCapture.Should().Be(capture);
        }
    }
}