diff --git a/src/bunit.core/ComponentParameterCollection.cs b/src/bunit.core/ComponentParameterCollection.cs index c70bee4d8..fb44ff3c9 100644 --- a/src/bunit.core/ComponentParameterCollection.cs +++ b/src/bunit.core/ComponentParameterCollection.cs @@ -1,13 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Rendering; namespace Bunit diff --git a/src/bunit.core/ComponentParameterCollectionBuilder.cs b/src/bunit.core/ComponentParameterCollectionBuilder.cs index 58162af94..748b1377a 100644 --- a/src/bunit.core/ComponentParameterCollectionBuilder.cs +++ b/src/bunit.core/ComponentParameterCollectionBuilder.cs @@ -4,7 +4,6 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; namespace Bunit diff --git a/src/bunit.core/ComponentParameterFactory.cs b/src/bunit.core/ComponentParameterFactory.cs index 210b1246f..718ad3723 100644 --- a/src/bunit.core/ComponentParameterFactory.cs +++ b/src/bunit.core/ComponentParameterFactory.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; diff --git a/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs b/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs index 1c49217b4..55562fdfd 100644 --- a/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs +++ b/src/bunit.core/Extensions/RenderedComponentRenderExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; namespace Bunit diff --git a/src/bunit.web/Asserting/CompareToDiffingExtensions.cs b/src/bunit.web/Asserting/CompareToDiffingExtensions.cs index dd1333989..8826ede83 100644 --- a/src/bunit.web/Asserting/CompareToDiffingExtensions.cs +++ b/src/bunit.web/Asserting/CompareToDiffingExtensions.cs @@ -4,6 +4,7 @@ using AngleSharp.Diffing.Core; using AngleSharp.Dom; using Bunit.Diffing; +using Bunit.Rendering; using Microsoft.Extensions.DependencyInjection; namespace Bunit @@ -27,7 +28,7 @@ public static IReadOnlyList CompareTo(this IRenderedFragment actual, stri if (expected is null) throw new ArgumentNullException(nameof(expected)); - var htmlParser = actual.Services.GetRequiredService(); + var htmlParser = actual.Services.GetRequiredService(); var expectedNodes = htmlParser.Parse(expected); return actual.Nodes.CompareTo(expectedNodes); diff --git a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs index c3fdc1d56..466dd1c9d 100644 --- a/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs +++ b/src/bunit.web/Asserting/MarkupMatchesAssertExtensions.cs @@ -1,6 +1,7 @@ using System; using AngleSharp.Dom; using Bunit.Diffing; +using Bunit.Rendering; using Microsoft.Extensions.DependencyInjection; namespace Bunit @@ -20,7 +21,7 @@ public static class MarkupMatchesAssertExtensions /// A custom user message to display in case the verification fails. public static void MarkupMatches(this string actual, string expected, string? userMessage = null) { - using var parser = new HtmlParser(); + using var parser = new BunitHtmlParser(); var actualNodes = parser.Parse(actual); var expectedNodes = parser.Parse(expected); actualNodes.MarkupMatches(expectedNodes, userMessage); @@ -39,7 +40,7 @@ public static void MarkupMatches(this string actual, IRenderedFragment expected, if (expected is null) throw new ArgumentNullException(nameof(expected)); - var actualNodes = actual.ToNodeList(expected.Services.GetRequiredService()); + var actualNodes = actual.ToNodeList(expected.Services.GetRequiredService()); actualNodes.MarkupMatches(expected, userMessage); } @@ -92,7 +93,7 @@ public static void MarkupMatches(this IRenderedFragment actual, string expected, if (expected is null) throw new ArgumentNullException(nameof(expected)); - var expectedNodes = expected.ToNodeList(actual.Services.GetRequiredService()); + var expectedNodes = expected.ToNodeList(actual.Services.GetRequiredService()); actual.Nodes.MarkupMatches(expectedNodes, userMessage); } @@ -239,11 +240,11 @@ public static void MarkupMatches(this INode actual, INodeList expected, string? throw new HtmlEqualException(diffs, expected, actual, userMessage); } - private static INodeList ToNodeList(this string markup, HtmlParser? htmlParser) + private static INodeList ToNodeList(this string markup, BunitHtmlParser? htmlParser) { if (htmlParser is null) { - using var newHtmlParser = new HtmlParser(); + using var newHtmlParser = new BunitHtmlParser(); return newHtmlParser.Parse(markup); } else diff --git a/src/bunit.web/Asserting/ShouldBeAdditionAssertExtensions.cs b/src/bunit.web/Asserting/ShouldBeAdditionAssertExtensions.cs index 9e0653564..200f1eaa1 100644 --- a/src/bunit.web/Asserting/ShouldBeAdditionAssertExtensions.cs +++ b/src/bunit.web/Asserting/ShouldBeAdditionAssertExtensions.cs @@ -3,6 +3,7 @@ using AngleSharp.Diffing.Core; using AngleSharp.Dom; using Bunit.Diffing; +using Bunit.Rendering; namespace Bunit { @@ -29,13 +30,13 @@ public static void ShouldBeAddition(this IDiff actualChange, string expectedChan var actual = actualChange as UnexpectedNodeDiff ?? throw new DiffChangeAssertException(actualChange.Result, DiffResult.Unexpected, "The change was not an addition."); INodeList expected; - if (actual.Test.Node.GetHtmlParser() is HtmlParser parser) + if (actual.Test.Node.GetHtmlParser() is BunitHtmlParser parser) { expected = parser.Parse(expectedChange); } else { - using var newParser = new HtmlParser(); + using var newParser = new BunitHtmlParser(); expected = newParser.Parse(expectedChange); } diff --git a/src/bunit.web/Asserting/ShouldBeRemovalAssertExtensions.cs b/src/bunit.web/Asserting/ShouldBeRemovalAssertExtensions.cs index 842a11456..70762d72f 100644 --- a/src/bunit.web/Asserting/ShouldBeRemovalAssertExtensions.cs +++ b/src/bunit.web/Asserting/ShouldBeRemovalAssertExtensions.cs @@ -3,6 +3,7 @@ using AngleSharp.Diffing.Core; using AngleSharp.Dom; using Bunit.Diffing; +using Bunit.Rendering; namespace Bunit { @@ -29,13 +30,13 @@ public static void ShouldBeRemoval(this IDiff actualChange, string expectedChang var actual = actualChange as MissingNodeDiff ?? throw new DiffChangeAssertException(actualChange.Result, DiffResult.Missing, "The change was not an removal."); INodeList expected; - if (actual.Control.Node.GetHtmlParser() is HtmlParser parser) + if (actual.Control.Node.GetHtmlParser() is BunitHtmlParser parser) { expected = parser.Parse(expectedChange); } else { - using var newParser = new HtmlParser(); + using var newParser = new BunitHtmlParser(); expected = newParser.Parse(expectedChange); } diff --git a/src/bunit.web/Asserting/ShouldBeTextChangeAssertExtensions.cs b/src/bunit.web/Asserting/ShouldBeTextChangeAssertExtensions.cs index 50ff27965..0693cf53a 100644 --- a/src/bunit.web/Asserting/ShouldBeTextChangeAssertExtensions.cs +++ b/src/bunit.web/Asserting/ShouldBeTextChangeAssertExtensions.cs @@ -5,6 +5,7 @@ using AngleSharp.Dom; using Bunit.Asserting; using Bunit.Diffing; +using Bunit.Rendering; namespace Bunit { @@ -39,7 +40,7 @@ public static void ShouldBeTextChange(this IDiff actualChange, string expectedCh var actual = actualChange as NodeDiff ?? throw new DiffChangeAssertException(actualChange.Result, DiffResult.Different, "The change was not a text change."); - var parser = actual.Control.Node.Owner.Context.GetService(); + var parser = actual.Control.Node.Owner.Context.GetService(); var expected = parser.Parse(expectedChange); ShouldBeTextChange(actualChange, expected, userMessage); diff --git a/src/bunit.web/ComponentTestFixture.cs b/src/bunit.web/ComponentTestFixture.cs index 0ac91a843..d152567c4 100644 --- a/src/bunit.web/ComponentTestFixture.cs +++ b/src/bunit.web/ComponentTestFixture.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; namespace Bunit diff --git a/src/bunit.web/Diffing/HtmlParser.cs b/src/bunit.web/Diffing/HtmlParser.cs deleted file mode 100644 index 303db6bee..000000000 --- a/src/bunit.web/Diffing/HtmlParser.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using AngleSharp; -using AngleSharp.Dom; -using AngleSharp.Html.Parser; -using Bunit.Rendering; - -namespace Bunit.Diffing -{ - /// - /// A AngleSharp based HTML Parse that can parse markup strings - /// into a . - /// - public sealed class HtmlParser : IDisposable - { - private readonly IBrowsingContext _context; - private readonly IHtmlParser _htmlParser; - private readonly IDocument _document; - - /// - /// Creates an instance of the parser with a AngleSharp context - /// without a registered. - /// - public HtmlParser() - { - var config = Configuration.Default - .WithCss() - .With(new HtmlComparer()) - .With(this); - - _context = BrowsingContext.New(config); - _htmlParser = _context.GetService(); - _document = _context.OpenNewAsync().Result; - } - - /// - /// Creates an instance of the parser with a AngleSharp context - /// with the registered. - /// - public HtmlParser(ITestRenderer testRenderer, HtmlComparer htmlComparer) - { - var config = Configuration.Default - .WithCss() - .With(testRenderer) // added to allow elements to find the renderer to trigger events - .With(htmlComparer) - .With(this); - - _context = BrowsingContext.New(config); - _htmlParser = _context.GetService(); - _document = _context.OpenNewAsync().Result; - } - - /// - /// Parses a markup HTML string using AngleSharps HTML5 parser. - /// - /// The markup to parse. - /// The . - public INodeList Parse(string markup) - { - return _htmlParser.ParseFragment(markup, _document.Body); - } - - /// - public void Dispose() - { - _document.Dispose(); - _context.Dispose(); - } - } -} diff --git a/src/bunit.web/Extensions/Internal/AngleSharpExtensions.cs b/src/bunit.web/Extensions/Internal/AngleSharpExtensions.cs index 4f54dd0ca..b8e6f3995 100644 --- a/src/bunit.web/Extensions/Internal/AngleSharpExtensions.cs +++ b/src/bunit.web/Extensions/Internal/AngleSharpExtensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using AngleSharp.Dom; using Bunit.Diffing; +using Bunit.Rendering; namespace Bunit { @@ -20,23 +21,23 @@ public static IEnumerable AsEnumerable(this INode node) } /// - /// Gets the stored in the s + /// Gets the stored in the s /// owning context, if one is available. /// /// - /// The or null if not found. - public static HtmlParser? GetHtmlParser(this INode? node) + /// The or null if not found. + public static BunitHtmlParser? GetHtmlParser(this INode? node) { - return node?.Owner.Context.GetService(); + return node?.Owner.Context.GetService(); } /// - /// Gets the stored in the s + /// Gets the stored in the s /// owning context, if one is available. /// /// - /// The or null if not found. - public static HtmlParser? GetHtmlParser(this INodeList nodes) + /// The or null if not found. + public static BunitHtmlParser? GetHtmlParser(this INodeList nodes) { return nodes?.Length > 0 ? nodes[0].GetHtmlParser() : null; } diff --git a/src/bunit.web/Extensions/TestServiceProviderExtensions.cs b/src/bunit.web/Extensions/TestServiceProviderExtensions.cs index 8b916c579..14734549e 100644 --- a/src/bunit.web/Extensions/TestServiceProviderExtensions.cs +++ b/src/bunit.web/Extensions/TestServiceProviderExtensions.cs @@ -26,7 +26,7 @@ public static IServiceCollection AddDefaultTestContextServices(this IServiceColl services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); return services; } diff --git a/src/bunit.web/RazorTesting/SnapshotTest.cs b/src/bunit.web/RazorTesting/SnapshotTest.cs index 2af48661e..80db66590 100644 --- a/src/bunit.web/RazorTesting/SnapshotTest.cs +++ b/src/bunit.web/RazorTesting/SnapshotTest.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using Bunit.Diffing; using Bunit.Extensions; using Bunit.RazorTesting; +using Bunit.Rendering; using Microsoft.AspNetCore.Components; namespace Bunit @@ -62,7 +62,7 @@ protected override async Task Run() private void VerifySnapshot(string inputHtml, string expectedHtml) { - using var parser = new HtmlParser(); + using var parser = new BunitHtmlParser(); var inputNodes = parser.Parse(inputHtml); var expectedNodes = parser.Parse(expectedHtml); diff --git a/src/bunit.web/Rendering/BunitHtmlParser.cs b/src/bunit.web/Rendering/BunitHtmlParser.cs new file mode 100644 index 000000000..bfcf4cb89 --- /dev/null +++ b/src/bunit.web/Rendering/BunitHtmlParser.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Html.Parser; +using Bunit.Diffing; + +namespace Bunit.Rendering +{ + /// + /// A AngleSharp based HTML Parse that can parse markup strings + /// into a . + /// + public sealed class BunitHtmlParser : IDisposable + { + private static readonly string[] TABLE_SUB_ELEMENTS = { "CAPTION", "COLGROUP", "TBODY", "TFOOT", "THEAD", }; + private const string TBODY_SUB_ELEMENT = "TR"; + private static readonly string[] TR_SUB_ELEMENTS = { "TD", "TH" }; + private const string COLGROUP_SUB_ELEMENT = "COL"; + private static readonly string[] SPECIAL_HTML_ELEMENTS = { "HTML", "HEAD", "BODY" }; + + private readonly IBrowsingContext _context; + private readonly IHtmlParser _htmlParser; + private readonly List _documents = new List(); + + /// + /// Creates an instance of the parser with a AngleSharp context + /// without a registered. + /// + public BunitHtmlParser() : this(Configuration.Default.WithCss().With(new HtmlComparer())) { } + + /// + /// Creates an instance of the parser with a AngleSharp context + /// with the registered. + /// + public BunitHtmlParser(ITestRenderer testRenderer, HtmlComparer htmlComparer) + : this(Configuration.Default.WithCss().With(testRenderer).With(htmlComparer)) { } + + private BunitHtmlParser(IConfiguration angleSharpConfiguration) + { + var config = angleSharpConfiguration.With(this); + _context = BrowsingContext.New(config); + _htmlParser = _context.GetService(); + } + + /// + /// Parses a markup HTML string using AngleSharps HTML5 parser. + /// + /// The markup to parse. + /// The . + public INodeList Parse(string markup) + { + if (markup is null) throw new ArgumentNullException(nameof(markup)); + var (ctx, matchedElement) = GetParseContext(markup).GetAwaiter().GetResult(); + + return ctx is null && matchedElement is not null + ? ParseSpecial(markup, matchedElement) + : _htmlParser.ParseFragment(markup, ctx); + } + + private INodeList ParseSpecial(string markup, string matchedElement) + { + var doc = _htmlParser.ParseDocument(markup); + + return matchedElement switch + { + "HTML" => new SingleNodeNodeList(doc.Body.ParentElement), + "HEAD" => new SingleNodeNodeList(doc.Head), + "BODY" => new SingleNodeNodeList(doc.Body), + _ => throw new InvalidOperationException($"{matchedElement} should not be parsed by {nameof(ParseSpecial)}.") + }; + } + + private async Task<(IElement? ctx, string? matchedElement)> GetParseContext(string markup) + { + var document = await GetNewDocument().ConfigureAwait(false); + var startIndex = markup.IndexOfFirstNonWhitespaceChar(); + + // verify that first non-whitespace characters is a '<' + if (markup.Length > 0 && markup[startIndex].IsTagStart()) + { + return GetParseContextFromTag(markup, startIndex, document); + } + else + { + return (document.Body, null); + } + } + + private static (IElement? ctx, string? matchedElement) GetParseContextFromTag(string markup, int startIndex, IDocument document) + { + IElement? context = null; + string? matchedElement; + + if (markup.StartsWithElements(TABLE_SUB_ELEMENTS, startIndex, out matchedElement)) + { + context = CreateTable(); + } + else if (markup.StartsWithElements(TR_SUB_ELEMENTS, startIndex, out matchedElement)) + { + context = CreateTable().AppendElement(document.CreateElement("tr")); + } + else if (markup.StartsWithElement(TBODY_SUB_ELEMENT, startIndex)) + { + context = CreateTable().AppendElement(document.CreateElement("tbody")); + matchedElement = TBODY_SUB_ELEMENT; + } + else if (markup.StartsWithElement(COLGROUP_SUB_ELEMENT, startIndex)) + { + context = CreateTable().AppendElement(document.CreateElement("colgroup")); + matchedElement = COLGROUP_SUB_ELEMENT; + } + else if (markup.StartsWithElements(SPECIAL_HTML_ELEMENTS, startIndex, out matchedElement)) { } + else + { + context = document.Body; + } + + return (context, matchedElement); + + IElement CreateTable() => document.Body.AppendElement(document.CreateElement("table")); + } + + private async Task GetNewDocument() + { + var result = await _context.OpenNewAsync().ConfigureAwait(false); + _documents.Add(result); + return result; + } + + /// + public void Dispose() + { + _context.Dispose(); + foreach (var doc in _documents) + { + doc.Dispose(); + } + } + + private class SingleNodeNodeList : INodeList + { + private readonly INode node; + public INode this[int index] + { + get + { + if (index != 0) throw new IndexOutOfRangeException(); + return node; + } + } + public int Length { get; } = 1; + public SingleNodeNodeList(INode node) => this.node = node ?? throw new ArgumentNullException(nameof(node)); + public IEnumerator GetEnumerator() + { + yield return node; + } + public void ToHtml(TextWriter writer, IMarkupFormatter formatter) => node.ToHtml(writer, formatter); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +} diff --git a/src/bunit.web/Rendering/Internal/BunitHtmlParserHelpers.cs b/src/bunit.web/Rendering/Internal/BunitHtmlParserHelpers.cs new file mode 100644 index 000000000..26e8594ba --- /dev/null +++ b/src/bunit.web/Rendering/Internal/BunitHtmlParserHelpers.cs @@ -0,0 +1,61 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Bunit.Rendering +{ + internal static class BunitHtmlParserHelpers + { + internal static bool StartsWithElements(this string markup, string[] tags, int startIndex, [NotNullWhen(true)] out string? matchedElement) + { + matchedElement = null; + for (int i = 0; i < tags.Length; i++) + { + if (markup.StartsWithElement(tags[i], startIndex)) + { + matchedElement = tags[i]; + return true; + } + } + return false; + } + + internal static bool StartsWithElement(this string markup, string tag, int startIndex) + { + var matchesTag = tag.Length + 1 < markup.Length - startIndex; + var charIndexAfterTag = tag.Length + startIndex + 1; + + if (matchesTag) + { + var charAfterTag = markup[charIndexAfterTag]; + matchesTag = char.IsWhiteSpace(charAfterTag) || + charAfterTag == '>' || + charAfterTag == '/'; + } + + // match characters in tag + for (int i = 0; i < tag.Length && matchesTag; i++) + { + matchesTag = char.ToUpperInvariant(markup[startIndex + i + 1]) == tag[i]; + } + + // look for start tags end - '>' + for (int i = charIndexAfterTag; i < markup.Length && matchesTag; i++) + { + if (markup[i] == '>') break; + } + + return matchesTag; + } + + internal static bool IsTagStart(this char c) => c == '<'; + + internal static int IndexOfFirstNonWhitespaceChar(this string markup) + { + for (int i = 0; i < markup.Length; i++) + { + if (!char.IsWhiteSpace(markup, i)) + return i; + } + return 0; + } + } +} diff --git a/src/bunit.web/Rendering/RenderedFragment.cs b/src/bunit.web/Rendering/RenderedFragment.cs index fe5f4475d..fcedac747 100644 --- a/src/bunit.web/Rendering/RenderedFragment.cs +++ b/src/bunit.web/Rendering/RenderedFragment.cs @@ -4,7 +4,6 @@ using System.Threading; using AngleSharp.Diffing.Core; using AngleSharp.Dom; -using Bunit.Diffing; using Microsoft.Extensions.DependencyInjection; namespace Bunit.Rendering @@ -14,7 +13,7 @@ internal class RenderedFragment : IRenderedFragment { private readonly object _markupAccessLock = new object(); [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Instance is owned by the service provider and should not be disposed here.")] - private readonly HtmlParser _htmlParser; + private readonly BunitHtmlParser _htmlParser; private string _markup = string.Empty; private string? _snapshotMarkup; @@ -88,7 +87,7 @@ internal RenderedFragment(int componentId, IServiceProvider service) { ComponentId = componentId; Services = service; - _htmlParser = Services.GetRequiredService(); + _htmlParser = Services.GetRequiredService(); } /// diff --git a/src/bunit.web/TestContext.cs b/src/bunit.web/TestContext.cs index 9b0f73c33..7586f089a 100644 --- a/src/bunit.web/TestContext.cs +++ b/src/bunit.web/TestContext.cs @@ -1,6 +1,5 @@ using System; using Bunit.Extensions; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; namespace Bunit diff --git a/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs b/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs index 6dbf1a28a..ec632ae5a 100644 --- a/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs +++ b/tests/bunit.core.tests/ComponentParameterCollectionBuilderTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; using Shouldly; using Xunit; diff --git a/tests/bunit.core.tests/ComponentParameterCollectionTest.cs b/tests/bunit.core.tests/ComponentParameterCollectionTest.cs index b373ed904..e217c283a 100644 --- a/tests/bunit.core.tests/ComponentParameterCollectionTest.cs +++ b/tests/bunit.core.tests/ComponentParameterCollectionTest.cs @@ -1,15 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Bunit.Extensions; -using Bunit.Rendering; -using Bunit.TestAssets.SampleComponents; using Bunit.TestDoubles.JSInterop; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.Extensions.DependencyInjection; using Shouldly; using Xunit; diff --git a/tests/bunit.core.tests/ComponentParameterFactoryTest.cs b/tests/bunit.core.tests/ComponentParameterFactoryTest.cs index ba5914282..a853af36a 100644 --- a/tests/bunit.core.tests/ComponentParameterFactoryTest.cs +++ b/tests/bunit.core.tests/ComponentParameterFactoryTest.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Bunit.Rendering; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Shouldly; diff --git a/tests/bunit.core.tests/ShouldlyExtensions.cs b/tests/bunit.core.tests/ShouldlyExtensions.cs index d0a396405..68294bc22 100644 --- a/tests/bunit.core.tests/ShouldlyExtensions.cs +++ b/tests/bunit.core.tests/ShouldlyExtensions.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Bunit.Rendering; -using Microsoft.AspNetCore.Components; using Shouldly; namespace Bunit diff --git a/tests/bunit.web.tests/EventDispatchExtensions/TriggerEventSpy.cs b/tests/bunit.web.tests/EventDispatchExtensions/TriggerEventSpy.cs index 2517b831b..2f9f4bce5 100644 --- a/tests/bunit.web.tests/EventDispatchExtensions/TriggerEventSpy.cs +++ b/tests/bunit.web.tests/EventDispatchExtensions/TriggerEventSpy.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using AngleSharp.Dom; -using Bunit.Rendering; using Bunit.TestAssets.SampleComponents; using Microsoft.AspNetCore.Components; diff --git a/tests/bunit.web.tests/Rendering/BunitHtmlParserTest.cs b/tests/bunit.web.tests/Rendering/BunitHtmlParserTest.cs new file mode 100644 index 000000000..edc0a34ea --- /dev/null +++ b/tests/bunit.web.tests/Rendering/BunitHtmlParserTest.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AngleSharp.Dom; +using Shouldly; +using Xunit; + +namespace Bunit.Rendering +{ + public class BunitHtmlParserTest + { + private static readonly BunitHtmlParser Parser = new BunitHtmlParser(); + + /// + /// All HTML5 elements according to https://developer.mozilla.org/en-US/docs/Web/HTML/Element + /// + public static readonly IEnumerable BODY_HTML_ELEMENTS = (new[]{ + "base","link","meta","style","title", + "address","article","aside","footer","header","h1", "h2", "h3", "h4", "h5", "h6","hgroup","main","nav","section", + "blockquote","dd","div","dl","dt","figcaption","figure","hr","li","main","ol","p","pre","ul", + "a","abbr","b","bdi","bdo","br","cite","code","data","dfn","em","i","kbd","mark","q","rb","rp","rt","rtc","ruby","s","samp","small","span","strong","sub","sup","time","u","var","wbr", + "area","audio","img","map","track","video", + "embed","iframe","object","param","picture","source", + "canvas","noscript","script", + "del","ins", + "caption","col","colgroup","table","tbody","td","tfoot","th","thead","tr", + "button","datalist","fieldset","form","input","label","legend","meter","optgroup","option","output","progress","select","textarea", + "details","dialog","menu","summary", + "slot","template", + "acronym","applet","basefont","bgsound","big","blink","center","command","content","dir","element","font","keygen","listing","marquee","menuitem","multicol","nextid","nobr","noembed","noframes","plaintext","shadow","spacer","strike","tt","xmp", + // "frame","frameset","image","isindex" // not supported + }).Select(x => new[] { x }); + + [Fact(DisplayName = "Parse() called with null")] + public void ParseCalledWithNull() + { + Should.Throw(() => Parser.Parse(null!)); + } + + [Theory(DisplayName = "Parse() called with text only")] + [InlineData(" ")] + [InlineData("FOO BAR")] + public void ParseWithWhitespaceOnly(string text) + { + var actual = Parser.Parse(text); + + actual.ShouldHaveSingleItem().TextContent.ShouldBe(text); + } + + [Theory(DisplayName = "Parse() passed ")] + [MemberData(nameof(BODY_HTML_ELEMENTS))] + public void Test001(string elementName) + { + var actual = Parser.Parse($@"<{elementName} id=""{elementName}"">").ToList(); + + VerifyElementParsedWithId(elementName, actual); + } + + [Theory(DisplayName = "Parse() passed with whitespace before")] + [MemberData(nameof(BODY_HTML_ELEMENTS))] + public void Test002(string elementName) + { + var actual = Parser.Parse($@" {'\t'}{'\n'}{'\r'}{Environment.NewLine} <{elementName} id=""{elementName}"">").ToList(); + + VerifyElementParsedWithId(elementName, actual); + } + + [Theory(DisplayName = "Parse() passed ")] + [MemberData(nameof(BODY_HTML_ELEMENTS))] + public void Test003(string elementName) + { + var actual = Parser.Parse($@"<{elementName}>").ToList(); + + actual.ShouldHaveSingleItem() + .ShouldBeAssignableTo() + .NodeName.ShouldBe(elementName, StringCompareShould.IgnoreCase); + } + + [Theory(DisplayName = "Parse() passed ")] + [MemberData(nameof(BODY_HTML_ELEMENTS))] + public void Test004(string elementName) + { + var actual = Parser.Parse($@"<{elementName}/>").ToList(); + + actual.ShouldHaveSingleItem() + .ShouldBeAssignableTo() + .NodeName.ShouldBe(elementName, StringCompareShould.IgnoreCase); + } + + [Theory(DisplayName = "Parse() passed ").ToList(); + + VerifyElementParsedWithId(elementName, actual); + } + + private static void VerifyElementParsedWithId(string expectedElementName, List actual) + { + var elm = actual.OfType() + .ShouldHaveSingleItem() + .ShouldBeAssignableTo(); + + elm.ShouldSatisfyAllConditions( + () => elm.NodeName.ShouldBe(expectedElementName, StringCompareShould.IgnoreCase), + () => elm.Id.ShouldBe(expectedElementName) + ); + } + } +} diff --git a/tests/bunit.web.tests/TestDoubles/JSInterop/JSRuntimeAssertExtensionsTest.cs b/tests/bunit.web.tests/TestDoubles/JSInterop/JSRuntimeAssertExtensionsTest.cs index c6f2d271c..9bdf2bce8 100644 --- a/tests/bunit.web.tests/TestDoubles/JSInterop/JSRuntimeAssertExtensionsTest.cs +++ b/tests/bunit.web.tests/TestDoubles/JSInterop/JSRuntimeAssertExtensionsTest.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using AngleSharp.Dom; using Bunit.Asserting; -using Bunit.Diffing; +using Bunit.Rendering; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using Moq; @@ -116,7 +116,7 @@ public void Test201() [Fact(DisplayName = "ShouldBeElementReferenceTo throws if element reference does not point to the provided element")] public void Test202() { - using var htmlParser = new HtmlParser(); + using var htmlParser = new BunitHtmlParser(); var elmRef = new ElementReference(Guid.NewGuid().ToString()); var elm = (IElement)htmlParser.Parse($"

").First();