verifytests / verify.blazor Goto Github PK
View Code? Open in Web Editor NEWSupport for rendering a Blazor Component to a verified file via bunit or raw Blazor rendering.
License: MIT License
Support for rendering a Blazor Component to a verified file via bunit or raw Blazor rendering.
License: MIT License
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:
RenderedFragmentConverter
does not seem to be used?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?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.
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"));
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.