Git Product home page Git Product logo

verify.blazor's People

Contributors

actions-user avatar dependabot-preview[bot] avatar dependabot[bot] avatar egil avatar simoncropp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

blazorhub egil

verify.blazor's Issues

Utilizing bUnits parser and comparer

Hi @SimonCropp et al,

I have been experimenting with Verify.Bunit and have some ideas for improving it, such that it leverages the pretty printer and differ built into bUnit. This ensures that bUnit's blazor:xxx attributes are removed and ignored, and a diff is performed using AngleSharp.Diffing with the same configuration that bUnits "inline snapshot testing" method MarkupMatchces uses.

My attempt at a VerifyBunit type looks like this, with lots of copy paste from the current implementation:

using AngleSharp;
using AngleSharp.Diffing.Core;
using AngleSharp.Dom;
using AngleSharp.Text;
using Bunit.Diffing;
using Bunit.Rendering;
using Microsoft.AspNetCore.Components;
using System.Runtime.CompilerServices;

namespace VerifyTests;

public static class VerifyBunit
{
    private const string BunitFileExtension = "html";

    public static bool Initialized { get; private set; }

    public static void Initialize(bool verifyMarkupOnly = true)
    {
        if (Initialized)
        {
            throw new("Already Initialized");
        }

        Initialized = true;

        InnerVerifier.ThrowIfVerifyHasBeenRun();

        VerifierSettings.RegisterFileConverter<IRenderedFragment>(verifyMarkupOnly ? ConvertFragmentMarkupOnly : ConvertFragment);
        VerifierSettings.AddExtraSettings(settings => settings.Converters.Add(new RenderedFragmentConverter()));
        VerifierSettings.RegisterStringComparer(BunitFileExtension, BunitMarkupComparer);
    }

    private static Task<CompareResult> BunitMarkupComparer(string received, string verified, IReadOnlyDictionary<string, object> context)
    {
        using var parser = new BunitHtmlParser();
        var receivedNodes = parser.Parse(received);
        var verifiedNodes = parser.Parse(verified);
        var diffs = receivedNodes.CompareTo(verifiedNodes);

        var result = diffs.Count == 0
            ? CompareResult.Equal
              : CompareResult.NotEqual(CreateDiffMessage(received, verified, diffs));

        return Task.FromResult(result);
    }

    private static string CreateDiffMessage(string received, string verified, IReadOnlyList<IDiff> diffs)
    {
        var builder = StringBuilderPool.Obtain();
        builder.AppendLine();
        builder.AppendLine("HTML comparison failed. The following errors were found:");

        for (int i = 0; i < diffs.Count; i++)
        {
            builder.Append($"  {i + 1}: ");
            builder.AppendLine(diffs[i] switch
            {
                NodeDiff diff when diff.Target == DiffTarget.Text && diff.Control.Path.Equals(diff.Test.Path, StringComparison.Ordinal)
                    => $"The text in {diff.Control.Path} is different.",
                NodeDiff diff when diff.Target == DiffTarget.Text
                    => $"The expected {NodeName(diff.Control)} at {diff.Control.Path} and the actual {NodeName(diff.Test)} at {diff.Test.Path} is different.",
                NodeDiff diff when diff.Control.Path.Equals(diff.Test.Path, StringComparison.Ordinal)
                    => $"The {NodeName(diff.Control)}s at {diff.Control.Path} are different.",
                NodeDiff diff => $"The expected {NodeName(diff.Control)} at {diff.Control.Path} and the actual {NodeName(diff.Test)} at {diff.Test.Path} are different.",
                AttrDiff diff when diff.Control.Path.Equals(diff.Test.Path, StringComparison.Ordinal)
                    => $"The values of the attributes at {diff.Control.Path} are different.",
                AttrDiff diff => $"The value of the attribute {diff.Control.Path} and actual attribute {diff.Test.Path} are different.",
                MissingNodeDiff diff => $"The {NodeName(diff.Control)} at {diff.Control.Path} is missing.",
                MissingAttrDiff diff => $"The attribute at {diff.Control.Path} is missing.",
                UnexpectedNodeDiff diff => $"The {NodeName(diff.Test)} at {diff.Test.Path} was not expected.",
                UnexpectedAttrDiff diff => $"The attribute at {diff.Test.Path} was not expected.",
                _ => throw new SwitchExpressionException($"Unknown diff type detected: {diffs[i].GetType()}"),
            });
        }

        builder.AppendLine();
        builder.AppendLine("Actual HTML:");
        builder.AppendLine();
        builder.AppendLine(received);
        builder.AppendLine();
        builder.AppendLine("Expected HTML:");
        builder.AppendLine();
        builder.AppendLine(verified);

        return builder.ToPool();

        static string NodeName(ComparisonSource source) => source.Node.NodeType.ToString().ToLowerInvariant();
    }

    private static ConversionResult ConvertFragmentMarkupOnly(IRenderedFragment fragment, IReadOnlyDictionary<string, object> context)
    {
        var markup = fragment.Nodes.ToHtml(DiffMarkupFormatter.Instance).Trim();
        return new ConversionResult(null, BunitFileExtension, markup);
    }

    private static ConversionResult ConvertFragment(IRenderedFragment fragment, IReadOnlyDictionary<string, object> context)
    {
        var markup = fragment.Nodes.ToHtml(DiffMarkupFormatter.Instance).Trim();
        var allNodes = fragment.Nodes.Select(x => x.GetDescendantsAndSelf().Count()).Sum();
        var info = new ComponentInfo(GetInstance(fragment), allNodes);
        return new ConversionResult(info, BunitFileExtension, markup);
    }

    private static IComponent? GetInstance(IRenderedFragment fragment)
    {
        var type = fragment.GetType();
        if (!type.IsGenericType)
        {
            return null;
        }

        var renderComponentInterface = type
            .GetInterfaces()
            .SingleOrDefault(_ =>
                _.IsGenericType &&
                _.GetGenericTypeDefinition() == typeof(IRenderedComponentBase<>));

        if (renderComponentInterface == null)
        {
            return null;
        }

        var instanceProperty = renderComponentInterface.GetProperty("Instance")!;
        return (IComponent)instanceProperty.GetValue(fragment)!;
    }

    private sealed class ComponentInfo
    {
        public string? Component { get; }

        public IComponent? Instance { get; }

        public int NodeCount { get; }

        public ComponentInfo(IComponent? instance, int nodeCount)
        {
            Component = instance?.GetType().FullName;
            Instance = instance;
            NodeCount = nodeCount;
        }
    }

    private sealed class RenderedFragmentConverter : WriteOnlyJsonConverter<IRenderedFragment>
    {
        public override void Write(VerifyJsonWriter writer, IRenderedFragment fragment)
        {
            writer.WriteStartObject();

            writer.WriteMember(fragment, GetInstance(fragment), "Instance");

            writer.WriteMember(fragment, fragment.Markup, "Markup");

            writer.WriteEndObject();
        }
    }
}

The above code compiles with the following package references:

Verify.Xunit
bunit

A few questions/comments:

  1. RenderedFragmentConverter does not seem to be used?
  2. It seems redundant to convert fragment.Nodes to markup just to convert them back to nodes again in the comparer. But as far as I can tell there is no way around this with Verify, right?
  3. As mentioned, this utilizes the differ built-in to bUnit. That has the advantage of avoiding tests breaking if bUnit internally makes changes, and it handles some of the special attributes bUnit adds to the output.
  4. This defaults to using the semantic comparer (based on Anglesharp.Diffing) that bUnit comes with, which has been configured to ignore bUnits attribute in the markup, and it includes a custom node matcher that is needed in some circumstances.
  5. Ideally, users should not be asserting against the internal state of a component. Even though the component is a public thing, accessing its parameters and other public properties is not considered good practice in frontend testing, since you are interacting and accessing the component in a way that the end user will not. The end user will look at the markup only. Thus, by default, I suggest only verifying the produced markup.
  6. I did attempt to set BunitFileExtension to bunit, such that the compare would only be used with it, avoiding any conflicts with non-IRenderedFragment compares that may be registered. But it does not seem to be possible.

I am not super familiar with Verify so I wanted to dump my suggestion here instead of opening a PR.

Add attribute scrubbing to documentation for blazor:elementreference

I am trying to render Blazor components, but Blazor adds the "blazor:elementreference" attribute which changes every time we run the tests using bUnit.

Generated code:

<span style="font-size: 12px; text-align: center; margin: 0 auto; display: table" blazor:onclick="1" blazor:elementreference="e35f1ea4-22ae-4c45-8d3b-4f6f07877f10">Test value</span>

In this case, this result is rendered by the following code:

<Blazorise.Text Style="@TextStyle">@Text</Blazorise.Text>

Before submitting a potential PR to update the docs, would you be interested in this?

HtmlPrettyPrint.All(nodes => nodes.ScrubAttributes("blazor:elementreference"));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.