diff --git a/CHANGELOG.md b/CHANGELOG.md index 9120658..72e8386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.0.0 + +- Enabled using `diff:ignoreAttributes` and `diff:ignoreChildren` together on the same element. +- Corrected list of attributes that is considered as boolean attributes. Removed `hidden`, added `inert`, `playsinline`, `shadowrootclonable`, `shadowrootdelegatesfocus`, and `shadowrootserializable`. +- Upgrade to v1.x of AngleSharp. + +# 0.18.2 + +- Changed `CompareStrategy` such that it now can control the `IDiff` type that should be returned in case a difference is found in a comparison. This allows a comparer to embed additional context in the `IDiff` object. By [@SebastianStehle](https://github.com/SebastianStehle). +- Changed `ElementComparer` to skip comparing two nodes of different types. By [@SebastianStehle](https://github.com/SebastianStehle). + # 0.18.1 - Fixed element comparer such that it can strictly check if the closing tags in the source markup is the same. diff --git a/src/.editorconfig b/src/.editorconfig index 8dea088..daaaa18 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -225,3 +225,6 @@ dotnet_naming_style.fields_begin_with__.required_prefix = _ dotnet_naming_style.fields_begin_with__.required_suffix = dotnet_naming_style.fields_begin_with__.word_separator = dotnet_naming_style.fields_begin_with__.capitalization = camel_case + +# MA0012: Do not raise reserved exception type +dotnet_diagnostic.MA0012.severity = none \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj b/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj index c3605ce..ec15b72 100644 --- a/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj +++ b/src/AngleSharp.Diffing.Tests/AngleSharp.DiffingTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false AngleSharp.Diffing.Tests AngleSharp.Diffing @@ -10,18 +10,18 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs b/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs index d7ec516..d8cc0ba 100644 --- a/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs +++ b/src/AngleSharp.Diffing.Tests/Core/AttributeComparisonTest.cs @@ -65,7 +65,7 @@ public void Test005() var test = ToAttributeComparisonSource(@"
", "foo"); var comparison = new AttributeComparison(control, test); - var (actualCtrlElm, actualTestElm) = comparison.GetAttributeElements(); + var (actualCtrlElm, actualTestElm) = comparison.AttributeElements; actualCtrlElm.ShouldBe(control.ElementSource.Node); actualTestElm.ShouldBe(test.ElementSource.Node); @@ -135,7 +135,7 @@ public void Test005() var test = ToAttributeComparisonSource(@"
", "foo"); var comparison = new AttributeComparison(control, test); - var (actualCtrlElm, actualTestElm) = comparison.GetAttributeElements(); + var (actualCtrlElm, actualTestElm) = comparison.AttributeElements; actualCtrlElm.ShouldBe(control.ElementSource.Node); actualTestElm.ShouldBe(test.ElementSource.Node); diff --git a/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs b/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs index 901b004..e5609b8 100644 --- a/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/Core/DiffingEngineTestBase.cs @@ -2,7 +2,7 @@ namespace AngleSharp.Diffing.Core; public abstract class DiffingEngineTestBase : DiffingTestBase { - public DiffingEngineTestBase(DiffingTestFixture fixture) : base(fixture) + protected DiffingEngineTestBase(DiffingTestFixture fixture) : base(fixture) { } @@ -24,7 +24,7 @@ protected static HtmlDiffer CreateHtmlDiffer( ); } - private class MockDiffingStrategy : IDiffingStrategy + private sealed class MockDiffingStrategy : IDiffingStrategy { private readonly Func? _nodeFilter; private readonly Func? _attrFilter; diff --git a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs index 6fc8cc6..1383304 100644 --- a/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs @@ -103,17 +103,46 @@ public void WhenNodesAreDifferentADiffIsReturned() var results = sut.Compare(nodes, nodes).ToList(); results.Count.ShouldBe(3); - results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + results[0].ShouldBeAssignableTo().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("P"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Element) ); - results[1].ShouldBeOfType().ShouldSatisfyAllConditions( + results[1].ShouldBeAssignableTo().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("#comment"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Comment) ); - results[2].ShouldBeOfType().ShouldSatisfyAllConditions( + results[2].ShouldBeAssignableTo().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("#text"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Text) + ); + } + + [Fact(DisplayName = "When matched control/test nodes are different, a custom diff is returned")] + public void WhenNodesAreDifferentADiffIsReturnedWithCustomDiff() + { + var nodes = ToNodeList("

textnode"); + var sut = CreateHtmlDiffer( + nodeMatcher: OneToOneNodeListMatcher, + nodeFilter: NoneNodeFilter, + nodeComparer: DiffResultCustomNodeComparer); + + var results = sut.Compare(nodes, nodes).ToList(); + + results.Count.ShouldBe(3); + results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("P"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Element) + ); + results[1].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Node.NodeName.ShouldBe("#comment"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Comment) + ); + results[2].ShouldBeOfType().ShouldSatisfyAllConditions( diff => diff.Control.Node.NodeName.ShouldBe("#text"), diff => diff.Result.ShouldBe(DiffResult.Different), diff => diff.Target.ShouldBe(DiffTarget.Text) @@ -237,6 +266,30 @@ public void WhenMatchedAttrsAreDiffAttrDiffIsReturned() ); } + [Fact(DisplayName = "When matched control/test attributes are different, a diff is returned with custom diff")] + public void WhenMatchedAttrsAreDiffAttrDiffIsReturnedWithCustomDiff() + { + var nodes = ToNodeList(@"

"); + + var sut = CreateHtmlDiffer( + nodeMatcher: OneToOneNodeListMatcher, + nodeFilter: NoneNodeFilter, + nodeComparer: SameResultNodeComparer, + attrMatcher: AttributeNameMatcher, + attrFilter: NoneAttrFilter, + attrComparer: DiffResultCustomAttrComparer); + + var results = sut.Compare(nodes, nodes).ToList(); + + results.Count.ShouldBe(1); + results[0].ShouldBeOfType().ShouldSatisfyAllConditions( + diff => diff.Control.Attribute.Name.ShouldBe("id"), + diff => diff.Test.Attribute.Name.ShouldBe("id"), + diff => diff.Result.ShouldBe(DiffResult.Different), + diff => diff.Target.ShouldBe(DiffTarget.Attribute) + ); + } + [Fact(DisplayName = "When matched control/test attributes are the same, no diffs are returned")] public void WhenMatchedAttrsAreSameNoDiffIsReturned() { @@ -268,11 +321,11 @@ public void WhenBothTestAndControlHaveChildNodesTheseAreCompared() var results = sut.Compare(nodes, nodes).ToList(); results.Count.ShouldBe(5); - results[0].ShouldBeOfType().Control.Node.NodeName.ShouldBe("MAIN"); - results[1].ShouldBeOfType().Control.Node.NodeName.ShouldBe("H1"); - results[2].ShouldBeOfType().Control.Node.NodeValue.ShouldBe("foobar"); - results[3].ShouldBeOfType().Control.Node.NodeName.ShouldBe("P"); - results[4].ShouldBeOfType().Control.Node.NodeName.ShouldBe("#text"); + results[0].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("MAIN"); + results[1].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("H1"); + results[2].ShouldBeAssignableTo().Control.Node.NodeValue.ShouldBe("foobar"); + results[3].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("P"); + results[4].ShouldBeAssignableTo().Control.Node.NodeName.ShouldBe("#text"); } [Theory(DisplayName = "When only one of the control or test node in a comparison has child nodes, a missing/unexpected diff is returned")] @@ -288,7 +341,7 @@ public void OnlyOnePartHasChildNodes(string control, string test, Type expectedD var results = sut.Compare(ToNodeList(control), ToNodeList(test)).ToList(); results.Count.ShouldBe(2); - results[0].ShouldBeOfType(); + results[0].ShouldBeAssignableTo(); results[1].ShouldBeOfType(expectedDiffType); } @@ -309,8 +362,8 @@ public void ComparisonSourcesHaveCorrectType() results.Count.ShouldBe(2); - results[0].ShouldBeOfType().Control.SourceType.ShouldBe(ComparisonSourceType.Control); - results[0].ShouldBeOfType().Test.SourceType.ShouldBe(ComparisonSourceType.Test); + results[0].ShouldBeAssignableTo().Control.SourceType.ShouldBe(ComparisonSourceType.Control); + results[0].ShouldBeAssignableTo().Test.SourceType.ShouldBe(ComparisonSourceType.Test); results[1].ShouldBeOfType().Control.SourceType.ShouldBe(ComparisonSourceType.Control); results[1].ShouldBeOfType().Test.SourceType.ShouldBe(ComparisonSourceType.Test); } @@ -349,14 +402,14 @@ public void Test2() } [Theory(DisplayName = "When comparer returns SkipChildren flag from an element comparison, child nodes are not compared")] - [InlineData(CompareResult.Same | CompareResult.SkipChildren)] - [InlineData(CompareResult.Skip | CompareResult.SkipChildren)] - public void Test3(CompareResult compareResult) + [InlineData(CompareDecision.Same | CompareDecision.SkipChildren)] + [InlineData(CompareDecision.Skip | CompareDecision.SkipChildren)] + public void Test3(CompareDecision decision) { var sut = CreateHtmlDiffer( nodeMatcher: OneToOneNodeListMatcher, nodeFilter: NoneNodeFilter, - nodeComparer: c => c.Control.Node.NodeName == "P" ? compareResult : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"), + nodeComparer: c => c.Control.Node.NodeName == "P" ? new CompareResult(decision) : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"), attrMatcher: AttributeNameMatcher, attrFilter: NoneAttrFilter, attrComparer: SameResultAttrComparer @@ -368,14 +421,14 @@ public void Test3(CompareResult compareResult) } [Theory(DisplayName = "When comparer returns SkipAttributes flag from an element comparison, attributes are not compared")] - [InlineData(CompareResult.Same | CompareResult.SkipAttributes)] - [InlineData(CompareResult.Skip | CompareResult.SkipAttributes)] - public void Test4(CompareResult compareResult) + [InlineData(CompareDecision.Same | CompareDecision.SkipAttributes)] + [InlineData(CompareDecision.Skip | CompareDecision.SkipAttributes)] + public void Test4(CompareDecision decision) { var sut = CreateHtmlDiffer( nodeMatcher: OneToOneNodeListMatcher, nodeFilter: NoneNodeFilter, - nodeComparer: c => compareResult, + nodeComparer: c => new CompareResult(decision), attrMatcher: AttributeNameMatcher, attrFilter: NoneAttrFilter, attrComparer: SameResultAttrComparer @@ -398,7 +451,7 @@ private static IEnumerable NoneNodeMatcher(IDiffContext ctx, SourceC private static Func> SpecificIndexNodeMatcher(int index) => (ctx, controlNodes, testNodes) => { - return new List { new Comparison(controlNodes[index], testNodes[index]) }; + return new List { new(controlNodes[index], testNodes[index]) }; }; private static IEnumerable OneToOneNodeListMatcher( @@ -411,6 +464,7 @@ private static IEnumerable OneToOneNodeListMatcher( #region NodeComparers private static CompareResult SameResultNodeComparer(Comparison comparison) => CompareResult.Same; private static CompareResult DiffResultNodeComparer(Comparison comparison) => CompareResult.Different; + private static CompareResult DiffResultCustomNodeComparer(Comparison comparison) => CompareResult.FromDiff(new CustomNodeDiff(comparison)); #endregion #region AttributeMatchers @@ -423,7 +477,7 @@ private static Func new List { - new AttributeComparison(ctrlAttrs[matchAttrName], testAttrs[matchAttrName] ) + new(ctrlAttrs[matchAttrName], testAttrs[matchAttrName] ) }; } @@ -452,5 +506,22 @@ private static Func SpecificAttrFilte #region AttributeComparers public static CompareResult SameResultAttrComparer(AttributeComparison comparison) => CompareResult.Same; public static CompareResult DiffResultAttrComparer(AttributeComparison comparison) => CompareResult.Different; + public static CompareResult DiffResultCustomAttrComparer(AttributeComparison comparison) => CompareResult.FromDiff(new CustomAttrDiff(comparison)); + #endregion + + #region CustomDiff + public sealed record CustomNodeDiff : NodeDiff + { + public CustomNodeDiff(in Comparison comparison) : base(comparison) + { + } + } + + public sealed record CustomAttrDiff : AttrDiff + { + public CustomAttrDiff(in AttributeComparison comparison) : base(comparison, AttrDiffKind.Unspecified) + { + } + } #endregion } \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs index 9287f03..9769990 100644 --- a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs +++ b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs @@ -20,8 +20,8 @@ public void Test001() [Fact(DisplayName = "Builder throws if null is passed to control and test")] public void Test002() { - Should.Throw(() => DiffBuilder.Compare(null!)).ParamName.ShouldBe(nameof(DiffBuilder.Control)); - Should.Throw(() => DiffBuilder.Compare("").WithTest(null!)).ParamName.ShouldBe(nameof(DiffBuilder.Test)); + Should.Throw(() => DiffBuilder.Compare(null!)).ParamName.ShouldBe("value"); + Should.Throw(() => DiffBuilder.Compare("").WithTest(null!)).ParamName.ShouldBe("value"); } [Fact(DisplayName = "Calling Build() with DefaultOptions() returns expected diffs")] diff --git a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs index e463258..41d41ce 100644 --- a/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs +++ b/src/AngleSharp.Diffing.Tests/DiffingTestBase.cs @@ -4,11 +4,18 @@ public abstract class DiffingTestBase : IClassFixture { private readonly DiffingTestFixture _testFixture; + + public static readonly TheoryData SameAndSkipCompareResult = new() + { + CompareResult.Same, + CompareResult.Skip, + }; + protected IDiffContext DummyContext { get; } = new DiffContext(default(IElement), default(IElement)); protected INodeList EmptyNodeList => ToNodeList(""); - public DiffingTestBase(DiffingTestFixture fixture) + protected DiffingTestBase(DiffingTestFixture fixture) { _testFixture = fixture; } diff --git a/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs b/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..0501c6d --- /dev/null +++ b/src/AngleSharp.Diffing.Tests/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Not relevant in tests.")] +[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Not relevant in tests")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs index e281fb8..d3fcafd 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs @@ -1,20 +1,21 @@ namespace AngleSharp.Diffing.Strategies.AttributeStrategies; - public class AttributeComparerTest : DiffingTestBase { public AttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } - [Fact(DisplayName = "When compare is called with a current decision of Same or Skip, the current decision is returned")] - public void Test001() + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test001(CompareResult currentResult) { var comparison = ToAttributeComparison(@"", "foo", - "", "bar"); + "", "bar"); - AttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - AttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + new BooleanAttributeComparer(BooleanAttributeComparision.Strict) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); } [Fact(DisplayName = "When two attributes has the same name and no value, the compare result is Same")] @@ -23,7 +24,9 @@ public void Test002() var comparison = ToAttributeComparison(@"", "foo", "", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When two attributes does not have the same name, the compare result is Different")] @@ -32,7 +35,9 @@ public void Test003() var comparison = ToAttributeComparison(@"", "foo", "", "bar"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Fact(DisplayName = "When two attribute values are the same, the compare result is Same")] @@ -41,7 +46,9 @@ public void Test004() var comparison = ToAttributeComparison(@"", "foo", @"", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When two attribute values are different, the compare result is Different")] @@ -50,7 +57,9 @@ public void Test005() var comparison = ToAttributeComparison(@"", "foo", @"", "foo"); - AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + AttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "When the control attribute is postfixed with :ignoreCase, " + diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs index 05d3d98..db9dcbe 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs @@ -8,13 +8,25 @@ public BooleanAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"", "allowfullscreen", @"", "allowfullscreen"); + + new BooleanAttributeComparer(BooleanAttributeComparision.Strict) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute names are not the same comparer returns different")] public void Test001() { var sut = new BooleanAttributeComparer(BooleanAttributeComparision.Strict); var comparison = ToAttributeComparison("", "foo", "", "bar"); - sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + sut.Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Fact(DisplayName = "When attribute name is not an boolean attribute, its current result is returned")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs index a9eb44f..5987846 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs @@ -6,6 +6,18 @@ public ClassAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison($@"

", "class", + $@"

", "class"); + + ClassAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Theory(DisplayName = "When a class attribute is compared, the order of individual " + "classes and multiple whitespace is ignored")] [InlineData("", "")] @@ -18,7 +30,9 @@ public void Test009(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When a class attribute is matched up with another attribute, the result is different")] @@ -27,7 +41,9 @@ public void Test010() var comparison = ToAttributeComparison(@"

", "class", @"

", "bar"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name))); } [Theory(DisplayName = "When there are different number of classes in the class attributes the result is different")] @@ -38,7 +54,9 @@ public void Test011(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Theory(DisplayName = "When the classes in the class attributes are different the result is different")] @@ -51,6 +69,8 @@ public void Test012(string controlClasses, string testClasses) var comparison = ToAttributeComparison($@"

", "class", $@"

", "class"); - ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + ClassAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } } diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs index 06cd19d..a59efc3 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs @@ -8,6 +8,20 @@ public IgnoreAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison( + @"

", "foo", + @"

", "foo" + ); + + IgnoreAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When a attribute does not contain have the ':ignore' postfix, the current decision is returned")] public void Test003() { diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs index 2b06a0f..6e54c4b 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/OrderingStyleAttributeComparerTest.cs @@ -6,13 +6,23 @@ public OrderingStyleAttributeComparerTest(DiffingTestFixture fixture) : base(fix { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"

", "style", @"

", "style"); + OrderingStyleAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute is not style the current decision is used")] public void Test001() { var comparison = ToAttributeComparison(@"

", "foo", @"

", "foo"); - StyleAttributeComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); - StyleAttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - StyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); + OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); } [Theory(DisplayName = "When style attributes has different values then Different is returned")] @@ -23,7 +33,10 @@ public void Test001() public void Test002(string control, string test) { var comparison = ToAttributeComparison(control, "style", test, "style"); - OrderingStyleAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + + OrderingStyleAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "Comparer should correctly ignore insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs index b2e617c..1595920 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/StyleAttributeComparerTest.cs @@ -6,6 +6,16 @@ public StyleAttributeComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToAttributeComparison(@"

", "style", @"

", "style"); + StyleAttributeComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Fact(DisplayName = "When attribute is not style the current decision is used")] public void Test001() { @@ -15,6 +25,7 @@ public void Test001() StyleAttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); } + [Theory(DisplayName = "When style attributes has different values then Different is returned")] [InlineData(@"

", @"

")] [InlineData(@"

", @"

")] @@ -23,7 +34,10 @@ public void Test001() public void Test002(string control, string test) { var comparison = ToAttributeComparison(control, "style", test, "style"); - StyleAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + + StyleAttributeComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value))); } [Fact(DisplayName = "Comparer should correctly ignore insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs new file mode 100644 index 0000000..b525104 --- /dev/null +++ b/src/AngleSharp.Diffing.Tests/Strategies/CommentStrategies/CommentComparerTest.cs @@ -0,0 +1,53 @@ +namespace AngleSharp.Diffing.Strategies.CommentStrategies; + +public class CommentComparerTest : DiffingTestBase +{ + public CommentComparerTest(DiffingTestFixture fixture) : base(fixture) + { + } + + [Theory(DisplayName = "When current result is same or skip, current result is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToComparison("", ""); + CommentComparer + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + + [Theory(DisplayName = "When control and test are comment with equal content, the result is Same")] + [InlineData("", "")] + [InlineData("", "")] + public void Test001(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + CommentComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.Same); + } + + [Theory(DisplayName = "When control and test are comment with unequal content, the result is Different")] + [InlineData("", "")] + [InlineData("", "")] + [InlineData("", "")] + public void Test002(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + CommentComparer + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new CommentDiff(comparison))); + } + + [Theory(DisplayName = "When input node is not a IComment node, comparer does not run nor change the current decision")] + [InlineData("foo", "bar")] + [InlineData("

", "

")] + public void Test003(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + + CommentComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); + CommentComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); + CommentComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + } +} diff --git a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs index 00e1e40..9f985a7 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/DiffingStrategyPipelineTest.cs @@ -6,7 +6,7 @@ public DiffingStrategyPipelineTest(DiffingTestFixture fixture) : base(fixture) { } - private FilterDecision NegateDecision(FilterDecision decision) => decision switch + private static FilterDecision NegateDecision(FilterDecision decision) => decision switch { FilterDecision.Keep => FilterDecision.Exclude, FilterDecision.Exclude => FilterDecision.Keep, @@ -71,8 +71,6 @@ public void Test003(FilterDecision expected) sut.Filter(new AttributeComparisonSource()).ShouldBe(expected); } -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. [Fact(DisplayName = "When no matcher strategy have been added, no comparisons are returned")] public void Test2() { @@ -81,8 +79,6 @@ public void Test2() sut.Match(null, null, (SourceCollection)null).ShouldBeEmpty(); sut.Match(null, null, (SourceMap)null).ShouldBeEmpty(); } -#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. [Fact(DisplayName = "Specialized node matchers are executed in the reverse order they are added in")] public void Test61() @@ -179,51 +175,51 @@ public void Test3() } [Theory(DisplayName = "Specialized comparers are executed in the order they are added in")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test8(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test8(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Specialized); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Theory(DisplayName = "Generalized comparers are executed in the reverse order they are added in")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test12321(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test12321(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Generalized); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Theory(DisplayName = "Generalized comparers are always executed before specialized comparers")] - [InlineData(CompareResult.Different, CompareResult.Same)] - [InlineData(CompareResult.Same, CompareResult.Different)] - public void Test8314(CompareResult first, CompareResult final) + [InlineData(CompareDecision.Different, CompareDecision.Same)] + [InlineData(CompareDecision.Same, CompareDecision.Different)] + public void Test8314(CompareDecision first, CompareDecision final) { var sut = new DiffingStrategyPipeline(); - sut.AddComparer((in Comparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in Comparison c, CompareResult current) => final, StrategyType.Specialized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => first, StrategyType.Generalized); - sut.AddComparer((in AttributeComparison c, CompareResult current) => final, StrategyType.Specialized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in Comparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(first), StrategyType.Generalized); + sut.AddComparer((in AttributeComparison c, CompareResult current) => new CompareResult(final), StrategyType.Specialized); - sut.Compare(new Comparison()).ShouldBe(final); - sut.Compare(new AttributeComparison()).ShouldBe(final); + sut.Compare(new Comparison()).ShouldBe(new CompareResult(final)); + sut.Compare(new AttributeComparison()).ShouldBe(new CompareResult(final)); } [Fact(DisplayName = "After two nodes has been matched, they are marked as matched in the source collection")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs index f0877ce..1061a98 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/ElementComparerTest.cs @@ -1,4 +1,6 @@ -namespace AngleSharp.Diffing.Strategies.ElementStrategies; +using AngleSharp.Diffing.Core.Diffs; + +namespace AngleSharp.Diffing.Strategies.ElementStrategies; public class ElementComparerTest : DiffingTestBase { @@ -6,6 +8,17 @@ public ElementComparerTest(DiffingTestFixture fixture) : base(fixture) { } + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) + { + var comparison = ToComparison("

", "

"); + + new ElementComparer(enforceTagClosing: false) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); + } + [Theory(DisplayName = "When control and test nodes have the same type and name and enforceTagClosing is false, the result is Same")] [InlineData("

", "

")] [InlineData("
", "
")] @@ -19,23 +32,28 @@ public void Test001(string controlHtml, string testHtml) .ShouldBe(CompareResult.Same); } - [Theory(DisplayName = "When control and test nodes have the a different type and name, the result is Different")] + [Theory(DisplayName = "When control and test nodes have the a name, the result is Different")] [InlineData("

", "

", false)] - [InlineData("

", "textnode", false)] - [InlineData("
", "", false)] - [InlineData("", "textnode", false)] - [InlineData("
", "
", true)] - [InlineData("", "", true)] [InlineData("
", "

", true)] - [InlineData("

", "textnode", true)] - [InlineData("
", "", true)] - [InlineData("", "textnode", true)] public void Test002(string controlHtml, string testHtml, bool enforceTagClosing) { var comparison = ToComparison(controlHtml, testHtml); + + new ElementComparer(enforceTagClosing) + .Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.Name))); + } + + [Theory(DisplayName = "When control and test nodes have the a different closing style, the result is Different")] + [InlineData("
", "
", true)] + [InlineData("", "", true)] + public void Test003(string controlHtml, string testHtml, bool enforceTagClosing) + { + var comparison = ToComparison(controlHtml, testHtml); + new ElementComparer(enforceTagClosing) .Compare(comparison, CompareResult.Unknown) - .ShouldBe(CompareResult.Different); + .ShouldBe(CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.ClosingStyle))); } [Theory(DisplayName = "When unknown node is used in comparison, but node name is equal, the result is Same")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs index 67bcd96..261a655 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreAttributesElementComparerTest.cs @@ -32,4 +32,19 @@ public void Test002(string controlHtml) IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes); IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes); } + + [Theory(DisplayName = "When a control element has both 'diff:ignoreChildren' and a 'diff:ignoreAttributes'")] + [InlineData("", @"")] + [InlineData("", @"")] + public void Test003(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.SkipAttributes); + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipAttributes); + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipChildrenAndAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes); + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipChildren).ShouldBe(CompareResult.SkipChildrenAndAttributes); + IgnoreAttributesElementComparer.Compare(comparison, CompareResult.SkipAttributes).ShouldBe(CompareResult.SkipAttributes); + } } diff --git a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs index 0fa89b9..96b064a 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/ElementStrategies/IgnoreChildrenElementComparerTest.cs @@ -32,4 +32,19 @@ public void Test002(string controlHtml) IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren); IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren); } + + [Theory(DisplayName = "When a control element has both 'diff:ignoreChildren' and a 'diff:ignoreAttributes'")] + [InlineData("", @"")] + [InlineData("", @"")] + public void Test003(string controlHtml, string testHtml) + { + var comparison = ToComparison(controlHtml, testHtml); + + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.SkipChildren); + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.SkipChildren); + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipChildrenAndAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes); + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipAttributes).ShouldBe(CompareResult.SkipChildrenAndAttributes); + IgnoreChildrenElementComparer.Compare(comparison, CompareResult.SkipChildren).ShouldBe(CompareResult.SkipChildren); + } } diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs index e37e669..a7db2a8 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/StyleSheetTextNodeComparerTest.cs @@ -32,7 +32,10 @@ public void Test001() { var comparison = ToStyleComparison(@"h1{background:#000;}", @"h1{color:#000;}"); - StyleSheetTextNodeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + var result = StyleSheetTextNodeComparer.Compare(comparison, CompareResult.Unknown); + + result.Decision.ShouldBe(CompareDecision.Different); + result.Diff.ShouldBeEquivalentTo(new StylesheetDiff(comparison)); } [Theory(DisplayName = "The comparer ignores insignificant whitespace")] diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs index 85d6e2b..7d18c0e 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeComparerTest.cs @@ -6,24 +6,22 @@ public TextNodeComparerTest(DiffingTestFixture fixture) : base(fixture) { } - [Fact(DisplayName = "When input node is not a IText node, comparer does not run nor change the current decision")] - public void Test2() + [Theory(DisplayName = "When current result is same or skip, the current decision is returned")] + [MemberData(nameof(SameAndSkipCompareResult))] + public void Test000(CompareResult currentResult) { - var comparison = ToComparison("

", "

"); - var sut = new TextNodeComparer(); + var comparison = ToComparison("hello world", " hello world "); - sut.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); - sut.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); - sut.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip); + new TextNodeComparer(WhitespaceOption.Preserve) + .Compare(comparison, currentResult) + .ShouldBe(currentResult); } - [Theory(DisplayName = "When option is Preserve or RemoveWhitespaceNodes, comparer does not run nor change the current decision")] - [InlineData(WhitespaceOption.Preserve)] - [InlineData(WhitespaceOption.RemoveWhitespaceNodes)] - public void Test5(WhitespaceOption whitespaceOption) + [Fact(DisplayName = "When input node is not a IText node, comparer does not run nor change the current decision")] + public void Test2() { - var comparison = ToComparison("hello world", " hello world "); - var sut = new TextNodeComparer(whitespaceOption); + var comparison = ToComparison("

", "

"); + var sut = new TextNodeComparer(); sut.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); sut.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); @@ -122,7 +120,8 @@ public void Test005(string tag) var testSource = ToComparisonSource("foo bar", ComparisonSourceType.Test); var comparison = new Comparison(controlSource, testSource); - sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different); + sut.Compare(comparison, CompareResult.Unknown) + .ShouldBe(CompareResult.FromDiff(new TextDiff(comparison))); } [Theory(DisplayName = "When the parent element is
 and the whitespace option is set " +
@@ -187,7 +186,8 @@ public void Test009(string controlHtml)
         var testSource = ToComparisonSource("hello world", ComparisonSourceType.Test);
         var comparison = new Comparison(controlSource, testSource);
 
-        sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
+        sut.Compare(comparison, CompareResult.Unknown)
+            .ShouldBe(CompareResult.FromDiff(new TextDiff(comparison)));
     }
 
     [Theory(DisplayName = "When diff:regex attribute is found on the immediate parent element, the control text is expected to a regex and that used when comparing to the test text node.")]
@@ -228,6 +228,4 @@ public void Test012(string controlHtml)
 
         sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same);
     }
-}
-
-
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs
index 1b8fc0a..3df2e00 100644
--- a/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs
+++ b/src/AngleSharp.Diffing.Tests/Strategies/TextNodeStrategies/TextNodeTestBase.cs
@@ -18,7 +18,7 @@ public abstract class TextNodeTestBase : DiffingTestBase
 
     public static readonly IEnumerable WhitespaceCharStrings = AllWhitespaceCharacters.Select(c => new string[] { c.ToString(CultureInfo.InvariantCulture) }).ToArray();
 
-    public TextNodeTestBase(DiffingTestFixture fixture) : base(fixture)
+    protected TextNodeTestBase(DiffingTestFixture fixture) : base(fixture)
     {
     }
 }
diff --git a/src/AngleSharp.Diffing.sln b/src/AngleSharp.Diffing.sln
index c93f6cd..83fb1ab 100644
--- a/src/AngleSharp.Diffing.sln
+++ b/src/AngleSharp.Diffing.sln
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29230.61
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35312.102
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngleSharp.Diffing", "AngleSharp.Diffing\AngleSharp.Diffing.csproj", "{2BFFA992-22C2-4A65-94D8-CA06E81D2364}"
 EndProject
@@ -9,7 +9,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngleSharp.DiffingTests", "
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E8E3C8B4-92C3-4DB7-B920-D28651E24A57}"
 	ProjectSection(SolutionItems) = preProject
-		..\.editorconfig = ..\.editorconfig
+		.editorconfig = .editorconfig
 		..\tools\anglesharp.cake = ..\tools\anglesharp.cake
 		..\build.cake = ..\build.cake
 		..\build.ps1 = ..\build.ps1
diff --git a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
index d115210..1c574ff 100644
--- a/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
+++ b/src/AngleSharp.Diffing/AngleSharp.Diffing.csproj
@@ -1,40 +1,44 @@
 
 
-  
-    netstandard2.0
-    true
-  
+	
+		netstandard2.0
+		true
+	
 
-  
-    Provides a complete diffing model of HTML.
-    AngleSharp.Diffing
-    AngleSharp
-    AngleSharp.Diffing
-    MIT
-    https://anglesharp.github.io
-    logo.png
-    https://raw.github.com/AngleSharp/AngleSharp.Diffing/master/logo.png
-    html html5 css css3 dom library diffing anglesharp diff difference compare comparison testing
-    Egil Hansen
-    https://github.com/AngleSharp/AngleSharp.Diffing
-    git
-    true
-    true
-    snupkg
-  
+	
+		Provides a complete diffing model of HTML.
+		AngleSharp.Diffing
+		AngleSharp
+		AngleSharp.Diffing
+		MIT
+		https://anglesharp.github.io
+		logo.png
+		https://raw.github.com/AngleSharp/AngleSharp.Diffing/master/logo.png
+		html html5 css css3 dom library diffing anglesharp diff difference compare comparison testing
+		Egil Hansen
+		https://github.com/AngleSharp/AngleSharp.Diffing
+		git
+		true
+		true
+		snupkg
+		$(NoWarn);NU5104
+	
 
-  
-    
-  
+	
+		
+	
 
-  
-    
-    
-  
+	
+		
+			all
+			runtime; build; native; contentfiles; analyzers
+		
+		
+		
+	
 
-  
-    
-    
-  
+	
+		
+	
 
 
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/AttributeComparison.cs b/src/AngleSharp.Diffing/Core/AttributeComparison.cs
index 9d9d5c9..5f69f71 100644
--- a/src/AngleSharp.Diffing/Core/AttributeComparison.cs
+++ b/src/AngleSharp.Diffing/Core/AttributeComparison.cs
@@ -1,47 +1,18 @@
+using System.Runtime.InteropServices;
+
 namespace AngleSharp.Diffing.Core;
 
 /// 
 /// A match between two attributes that should be compared.
 /// 
-public readonly struct AttributeComparison : IEquatable
+/// Gets the control attribute which the  attribute is supposed to match.
+/// Gets the test attribute which should be compared to the  attribute.
+[StructLayout(LayoutKind.Auto)]
+public readonly record struct AttributeComparison(in AttributeComparisonSource Control, in AttributeComparisonSource Test)
 {
-    /// 
-    /// Gets the control attribute which the  attribute is supposed to match.
-    /// 
-    public AttributeComparisonSource Control { get; }
-
-    /// 
-    /// Gets the test attribute which should be compared to the  attribute.
-    /// 
-    public AttributeComparisonSource Test { get; }
-
-    /// 
-    /// Create a attribute comparison match.
-    /// 
-    /// The attribute control source
-    /// The attribute test source
-    public AttributeComparison(in AttributeComparisonSource control, in AttributeComparisonSource test)
-    {
-        Control = control;
-        Test = test;
-    }
-
     /// 
     /// Returns the control and test elements which the control and test attributes belongs to.
     /// 
-    public (IElement ControlElement, IElement TestElement) GetAttributeElements()
+    public readonly (IElement ControlElement, IElement TestElement) AttributeElements
         => ((IElement)Control.ElementSource.Node, (IElement)Test.ElementSource.Node);
-
-    #region Equals and HashCode
-    /// 
-    public bool Equals(AttributeComparison other) => Control.Equals(other.Control) && Test.Equals(other.Test);
-    /// 
-    public override bool Equals(object? obj) => obj is AttributeComparison other && Equals(other);
-    /// 
-    public override int GetHashCode() => (Control, Test).GetHashCode();
-    /// 
-    public static bool operator ==(AttributeComparison left, AttributeComparison right) => left.Equals(right);
-    /// 
-    public static bool operator !=(AttributeComparison left, AttributeComparison right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
index 0c35d15..a5a1e68 100644
--- a/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
+++ b/src/AngleSharp.Diffing/Core/AttributeComparisonSource.cs
@@ -30,7 +30,7 @@ namespace AngleSharp.Diffing.Core;
     /// 
     /// Name of the attribute.
     /// The source of the element the attribute belongs to.
-    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
+    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Attribute names should be safe to lowercase.")]
     public AttributeComparisonSource(string attributeName, in ComparisonSource elementSource)
     {
         if (string.IsNullOrEmpty(attributeName))
@@ -44,16 +44,24 @@ public AttributeComparisonSource(string attributeName, in ComparisonSource eleme
         Path = $"{elementSource.Path}[{attribute.Name.ToLowerInvariant()}]";
     }
 
-    #region Equals and HashCode
     /// 
-    public bool Equals(AttributeComparisonSource other) => Object.ReferenceEquals(Attribute, other.Attribute) && Path.Equals(other.Path, StringComparison.Ordinal) && ElementSource.Equals(other.ElementSource);
+    public override string ToString() => $"{{ Type = Attribute, Path = {Path} }}";
+
+    /// 
+    public bool Equals(AttributeComparisonSource other)
+        => ReferenceEquals(Attribute, other.Attribute) // AngleSharp overrides Equals and == for it's types, so we're using ReferenceEquals to check if the instances are the same.
+        && Path.Equals(other.Path, StringComparison.Ordinal)
+        && ElementSource.Equals(other.ElementSource);
+
     /// 
     public override int GetHashCode() => (Attribute, ElementSource).GetHashCode();
+
     /// 
     public override bool Equals(object? obj) => obj is AttributeComparisonSource other && Equals(other);
+
     /// 
     public static bool operator ==(AttributeComparisonSource left, AttributeComparisonSource right) => left.Equals(right);
+
     /// 
     public static bool operator !=(AttributeComparisonSource left, AttributeComparisonSource right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/CompareDecision.cs b/src/AngleSharp.Diffing/Core/CompareDecision.cs
new file mode 100644
index 0000000..73d626d
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/CompareDecision.cs
@@ -0,0 +1,34 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents the decision of a comparison.
+/// 
+[Flags]
+public enum CompareDecision
+{
+    /// 
+    /// Use when the compare result is unknown.
+    /// 
+    None = 0,
+    /// 
+    /// Use when the two compared nodes or attributes are the same.
+    /// 
+    Same = 1,
+    /// 
+    /// Use when the two compared nodes or attributes are the different.
+    /// 
+    Different = 2,
+    /// 
+    /// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
+    /// 
+    Skip = 4,
+    /// 
+    /// Use when the comparison should skip any child-nodes.
+    /// 
+    SkipChildren = 8,
+    /// 
+    /// Use when the comparison should skip any attributes.
+    /// 
+    SkipAttributes = 16,
+}
+
diff --git a/src/AngleSharp.Diffing/Core/CompareResult.cs b/src/AngleSharp.Diffing/Core/CompareResult.cs
index 6ca7564..cd3de65 100644
--- a/src/AngleSharp.Diffing/Core/CompareResult.cs
+++ b/src/AngleSharp.Diffing/Core/CompareResult.cs
@@ -3,44 +3,54 @@
 /// 
 /// Represents a result of a comparison.
 /// 
-[Flags]
-public enum CompareResult
+/// Gets the latest  of the comparison.
+/// Gets the optional  related to the current .
+public readonly record struct CompareResult(CompareDecision Decision, IDiff? Diff = null)
 {
     /// 
     /// Use when the compare result is unknown.
     /// 
-    Unknown = 0,
+    public static readonly CompareResult Unknown;
+
     /// 
     /// Use when the two compared nodes or attributes are the same.
     /// 
-    Same = 1,
-    /// 
-    /// Use when the two compared nodes or attributes are the different.
-    /// 
-    Different = 2,
+    public static readonly CompareResult Same = new(CompareDecision.Same);
+
     /// 
     /// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
     /// 
-    Skip = 4,
+    public static readonly CompareResult Skip = new(CompareDecision.Skip);
+
     /// 
     /// Use when the comparison should skip any child-nodes.
     /// 
-    SkipChildren = 8,
+    public static readonly CompareResult SkipChildren = new(CompareDecision.SkipChildren);
+
     /// 
     /// Use when the comparison should skip any attributes.
     /// 
-    SkipAttributes = 16,
-}
+    public static readonly CompareResult SkipAttributes = new(CompareDecision.SkipAttributes);
 
-/// 
-/// Helper methods for 
-/// 
-public static class CompareResultExtensions
-{
     /// 
-    /// Checks if a  is either a  or .
+    /// Use when the comparison should skip any attributes.
     /// 
-    /// The compare result
-    public static bool IsSameOrSkip(this CompareResult compareResult) => compareResult == CompareResult.Same || compareResult == CompareResult.Skip;
-}
+    public static readonly CompareResult SkipChildrenAndAttributes = new(CompareDecision.SkipChildren | CompareDecision.SkipAttributes);
 
+    /// 
+    /// Use when the two compared nodes or attributes are the different.
+    /// 
+    public static CompareResult Different => new(CompareDecision.Different);
+
+    /// 
+    /// Use when the two compared nodes or attributes are the different.
+    /// 
+    /// The associated  describing the difference.
+    /// Returns a  with  set to .
+    public static CompareResult FromDiff(IDiff diff) => new(CompareDecision.Different, diff);
+
+    /// 
+    /// Checks if a  is either a  or .
+    /// 
+    public bool IsSameOrSkip => this == Same || this == Skip;
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/Comparison.cs b/src/AngleSharp.Diffing/Core/Comparison.cs
index 3050f18..41742e8 100644
--- a/src/AngleSharp.Diffing/Core/Comparison.cs
+++ b/src/AngleSharp.Diffing/Core/Comparison.cs
@@ -1,21 +1,15 @@
+using System.Runtime.InteropServices;
+
 namespace AngleSharp.Diffing.Core;
 
 /// 
 /// Represent a comparison between two nodes.
 /// 
-[DebuggerDisplay("Control: {Control.Path} | Test: {Test.Path}")]
-public readonly struct Comparison : IEquatable
+/// Gets the control source in the comparison.
+/// Gets the test source in the comparison.
+[StructLayout(LayoutKind.Auto)]
+public readonly record struct Comparison(in ComparisonSource Control, in ComparisonSource Test)
 {
-    /// 
-    /// Gets the control source in the comparison.
-    /// 
-    public ComparisonSource Control { get; }
-
-    /// 
-    /// Gets the test source in the comparison
-    /// 
-    public ComparisonSource Test { get; }
-
     /// 
     /// Gets whether the control and test nodes are of the same type and has the same name.
     /// 
@@ -23,17 +17,6 @@ public bool AreNodeTypesEqual
         => Control.Node.NodeType == Test.Node.NodeType
         && Control.Node.NodeName.Equals(Test.Node.NodeName, StringComparison.OrdinalIgnoreCase);
 
-    /// 
-    /// Creates a new comparison.
-    /// 
-    /// The control source in the comparison.
-    /// The tes tsource in the comparison.
-    public Comparison(in ComparisonSource control, in ComparisonSource test)
-    {
-        Control = control;
-        Test = test;
-    }
-
     /// 
     /// Try to get the control and test node as the  type.
     /// Returns true if both test and control node is of type , false otherwise.
@@ -55,17 +38,4 @@ public bool TryGetNodesAsType([NotNullWhen(true)]out TNode? controlNode,
             return false;
         }
     }
-
-    #region Equals and HashCode
-    /// 
-    public bool Equals(Comparison other) => Control.Equals(other.Control) && Test.Equals(other.Test);
-    /// 
-    public override bool Equals(object? obj) => obj is Comparison other && Equals(other);
-    /// 
-    public override int GetHashCode() => (Control, Test).GetHashCode();
-    /// 
-    public static bool operator ==(Comparison left, Comparison right) => left.Equals(right);
-    /// 
-    public static bool operator !=(Comparison left, Comparison right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/ComparisonSource.cs b/src/AngleSharp.Diffing/Core/ComparisonSource.cs
index ddf45ba..f900d64 100644
--- a/src/AngleSharp.Diffing/Core/ComparisonSource.cs
+++ b/src/AngleSharp.Diffing/Core/ComparisonSource.cs
@@ -3,7 +3,6 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a node source in a comparison.
 ///     
-[DebuggerDisplay("{Index} : {Path}")]
 public readonly struct ComparisonSource : IEquatable, IComparisonSource
 {
     private readonly int _hashCode;
@@ -65,7 +64,7 @@ public ComparisonSource(INode node, int index, string parentsPath, ComparisonSou
     /// 
     /// 
     /// 
-    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
+    [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Nodenames are safe to lower case.")]
     public static string GetNodePathSegment(INode node)
     {
         var index = GetPathIndex(node);
@@ -123,16 +122,21 @@ private static int GetPathIndex(INode node)
         throw new InvalidOperationException("Unexpected node tree state. The node was not found in its parents child nodes collection.");
     }
 
-    #region Equals and HashCode
+    /// 
+    public override string ToString() => $"{{ Type = {Node.NodeType}, Index = {Index}, Path = {Path} }}";
+
     /// 
     public bool Equals(ComparisonSource other) => Object.ReferenceEquals(Node, other.Node) && Index == other.Index && Path.Equals(other.Path, StringComparison.Ordinal) && SourceType == other.SourceType;
+
     /// 
     public override int GetHashCode() => _hashCode;
+
     /// 
     public override bool Equals(object? obj) => obj is ComparisonSource other && Equals(other);
+
     /// 
     public static bool operator ==(ComparisonSource left, ComparisonSource right) => left.Equals(right);
+
     /// 
     public static bool operator !=(ComparisonSource left, ComparisonSource right) => !left.Equals(right);
-    #endregion
 }
diff --git a/src/AngleSharp.Diffing/Core/SourceType.cs b/src/AngleSharp.Diffing/Core/ComparisonSourceType.cs
similarity index 100%
rename from src/AngleSharp.Diffing/Core/SourceType.cs
rename to src/AngleSharp.Diffing/Core/ComparisonSourceType.cs
diff --git a/src/AngleSharp.Diffing/Core/DiffTarget.cs b/src/AngleSharp.Diffing/Core/DiffTarget.cs
index af6579b..a4f400b 100644
--- a/src/AngleSharp.Diffing/Core/DiffTarget.cs
+++ b/src/AngleSharp.Diffing/Core/DiffTarget.cs
@@ -30,24 +30,3 @@ public enum DiffTarget
     /// 
     Text
 }
-
-/// 
-/// Helper methods for working with .
-/// 
-public static class NodeTypeExtensions
-{
-    /// 
-    /// Gets the diff target based on the node type.
-    /// 
-    /// Mode type to get the diff target off.
-    public static DiffTarget ToDiffTarget(this NodeType nodeType)
-    {
-        return nodeType switch
-        {
-            NodeType.Element => DiffTarget.Element,
-            NodeType.Comment => DiffTarget.Comment,
-            NodeType.Text => DiffTarget.Text,
-            _ => DiffTarget.Node
-        };
-    }
-}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
index 9924b9c..e667a0d 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/AttrDiff.cs
@@ -1,15 +1,20 @@
 namespace AngleSharp.Diffing.Core;
 
 /// 
-/// Represents an attribute difference
+/// Represents an attribute difference.
 /// 
-[DebuggerDisplay("Attribute diff: Control = {Control.Path}, Test = {Test.Path}")]
-public class AttrDiff : DiffBase
+public record class AttrDiff : DiffBase
 {
+    /// 
+    /// Gets the kind of the diff.
+    /// 
+    public AttrDiffKind Kind { get; }
+
     /// 
     /// Creates an .
     /// 
-    public AttrDiff(in AttributeComparison comparison) : base(comparison.Control, comparison.Test, DiffTarget.Attribute)
+    public AttrDiff(in AttributeComparison comparison, AttrDiffKind kind) : base(comparison.Control, comparison.Test, DiffTarget.Attribute)
     {
+        Kind = kind;
     }
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs b/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs
new file mode 100644
index 0000000..18f105a
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/AttrDiffKind.cs
@@ -0,0 +1,20 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Defines the reason for two attributes to be the different.
+/// 
+public enum AttrDiffKind
+{
+    /// 
+    /// The attribute difference is unspecified.
+    /// 
+    Unspecified = 0,
+    /// 
+    /// The name of the attribute is different.
+    /// 
+    Name,
+    /// 
+    /// The value of the attribute is different.
+    /// 
+    Value,
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs
new file mode 100644
index 0000000..e450eb2
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/CommentDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between two nodes.
+/// 
+public record class CommentDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public CommentDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
index 40273ce..2657337 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/DiffBase.cs
@@ -4,32 +4,11 @@ namespace AngleSharp.Diffing.Core;
 /// Represents a difference found during comparison.
 /// 
 /// 
-public abstract class DiffBase : IDiff where T : struct
+/// Gets the control source in the comparison.
+/// Gets the test source in the comparison.
+///  Gets the target type in that failed the comparison.
+public abstract record class DiffBase(T Control, T Test, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// Gets the control source in the comparison.
-    /// 
-    public T Control { get; }
-
-    /// 
-    /// Gets the test source in the comparison.
-    /// 
-    public T Test { get; }
-
     /// 
-    public DiffResult Result { get; }
-
-    /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Instantiate the 
-    /// 
-    protected DiffBase(in T control, in T test, DiffTarget target)
-    {
-        Control = control;
-        Test = test;
-        Result = DiffResult.Different;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Different;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs
new file mode 100644
index 0000000..bebd620
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/ElementDiff.cs
@@ -0,0 +1,21 @@
+namespace AngleSharp.Diffing.Core.Diffs;
+
+/// 
+/// Represents an element difference.
+/// 
+public record class ElementDiff : NodeDiff
+{
+    /// 
+    /// Gets the kind of the diff.
+    /// 
+    public ElementDiffKind Kind { get; }
+
+    /// 
+    /// Creates an .
+    /// 
+    public ElementDiff(in Comparison comparison, ElementDiffKind kind) : base(comparison)
+    {
+        Kind = kind;
+    }
+
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs b/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs
new file mode 100644
index 0000000..4404e03
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/ElementDiffKind.cs
@@ -0,0 +1,20 @@
+namespace AngleSharp.Diffing.Core.Diffs;
+
+/// 
+/// Defines the reason of two elements to be different.
+/// 
+public enum ElementDiffKind
+{
+    /// 
+    /// The attribute difference is unspecified.
+    /// 
+    Unspecified = 0,
+    /// 
+    /// The type/name of the elements are different.
+    /// 
+    Name,
+    /// 
+    /// The two elements have difference tag closing styles.
+    /// 
+    ClosingStyle,
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
index 2df6334..17d7c54 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingAttrDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a missing attribute in a test node.
 /// 
-[DebuggerDisplay("Missing Attribute: Control = {Control.Path}")]
-public class MissingAttrDiff : MissingDiffBase
+public record class MissingAttrDiff : MissingDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
index d172c55..ae076c6 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingDiffBase.cs
@@ -3,27 +3,10 @@
 /// 
 /// Represents a missing node or attribute in the test DOM tree.
 /// 
-/// 
-public abstract class MissingDiffBase : IDiff where T : struct
+/// Gets the control source that has the missing item.
+/// Gets the target type in that failed the comparison.
+public abstract record class MissingDiffBase(T Control, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// Gets the control source that has the missing item.
-    /// 
-    public T Control { get; }
-
-    /// 
-    public DiffResult Result { get; }
-
     /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Create a 
-    /// 
-    protected MissingDiffBase(in T control, DiffTarget target)
-    {
-        Control = control;
-        Result = DiffResult.Missing;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Missing;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
index e85dae1..6334838 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/MissingNodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a missing node in the test DOM tree.
 /// 
-[DebuggerDisplay("Missing {Target}: Control = {Control.Path}")]
-public class MissingNodeDiff : MissingDiffBase
+public record class MissingNodeDiff : MissingDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
index 0afd036..6fa5cec 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/NodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents a difference between two nodes.
 /// 
-[DebuggerDisplay("{Target} diff: Control = {Control.Path}, Test = {Test.Path}")]
-public class NodeDiff : DiffBase
+public record class NodeDiff : DiffBase
 {
     /// 
     /// Creates a .
@@ -12,4 +11,4 @@ public class NodeDiff : DiffBase
     public NodeDiff(in Comparison comparison) : base(comparison.Control, comparison.Test, comparison.Control.Node.NodeType.ToDiffTarget())
     {
     }
-}
+}
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs
new file mode 100644
index 0000000..99a97fa
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/StylesheetDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between the applied style of two nodes
+/// 
+public record class StylesheetDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public StylesheetDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs
new file mode 100644
index 0000000..6598593
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/Diffs/TextDiff.cs
@@ -0,0 +1,14 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Represents a difference between two texts.
+/// 
+public record class TextDiff : NodeDiff
+{
+    /// 
+    /// Creates a .
+    /// 
+    public TextDiff(in Comparison comparison) : base(comparison)
+    {
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
index c85d58c..7a7e364 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedAttrDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected attribute found in the test DOM tree.
 /// 
-[DebuggerDisplay("Unexpected Attribute: Test = {Test.Path}")]
-public class UnexpectedAttrDiff : UnexpectedDiffBase
+public record class UnexpectedAttrDiff : UnexpectedDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
index c675ae5..04c62be 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedDiffBase.cs
@@ -3,26 +3,10 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected node or attribute in the test DOM tree.
 /// 
-public abstract class UnexpectedDiffBase : IDiff where T : struct
+/// The source of the unexpected item in the test DOM tree.
+///  Gets the target type in that failed the comparison.
+public abstract record class UnexpectedDiffBase(T Test, DiffTarget Target) : IDiff where T : struct
 {
-    /// 
-    /// The source of the unexpected item in the test DOM tree.
-    /// 
-    public T Test { get; }
-
-    /// 
-    public DiffResult Result { get; }
-
     /// 
-    public DiffTarget Target { get; }
-
-    /// 
-    /// Creates a .
-    /// 
-    protected UnexpectedDiffBase(in T test, DiffTarget target)
-    {
-        Test = test;
-        Result = DiffResult.Unexpected;
-        Target = target;
-    }
+    public DiffResult Result => DiffResult.Unexpected;
 }
diff --git a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
index 43b5d02..a67eb50 100644
--- a/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
+++ b/src/AngleSharp.Diffing/Core/Diffs/UnexpectedNodeDiff.cs
@@ -3,8 +3,7 @@ namespace AngleSharp.Diffing.Core;
 /// 
 /// Represents an unexpected node in the test DOM tree.
 /// 
-[DebuggerDisplay("Unexpected {Target}: Test = {Test.Path}")]
-public class UnexpectedNodeDiff : UnexpectedDiffBase
+public record class UnexpectedNodeDiff : UnexpectedDiffBase
 {
     /// 
     /// Creates a .
diff --git a/src/AngleSharp.Diffing/Core/FilterDecision.cs b/src/AngleSharp.Diffing/Core/FilterDecision.cs
index 1274efb..ad80d89 100644
--- a/src/AngleSharp.Diffing/Core/FilterDecision.cs
+++ b/src/AngleSharp.Diffing/Core/FilterDecision.cs
@@ -14,19 +14,3 @@ public enum FilterDecision
     /// 
     Exclude
 }
-
-/// 
-/// Helper methods for .
-/// 
-public static class FilterDecisionExtensions
-{
-    /// 
-    /// Gets whether the  is .
-    /// 
-    public static bool IsExclude(this FilterDecision decision) => decision == FilterDecision.Exclude;
-
-    /// 
-    /// Gets whether the  is .
-    /// 
-    public static bool IsKeep(this FilterDecision decision) => decision == FilterDecision.Keep;
-}
diff --git a/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs b/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs
new file mode 100644
index 0000000..ba7d609
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/FilterDecisionExtensions.cs
@@ -0,0 +1,17 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Helper methods for .
+/// 
+public static class FilterDecisionExtensions
+{
+    /// 
+    /// Gets whether the  is .
+    /// 
+    public static bool IsExclude(this FilterDecision decision) => decision == FilterDecision.Exclude;
+
+    /// 
+    /// Gets whether the  is .
+    /// 
+    public static bool IsKeep(this FilterDecision decision) => decision == FilterDecision.Keep;
+}
diff --git a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
index f0f9aa8..575ca96 100644
--- a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
+++ b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
@@ -1,3 +1,5 @@
+using AngleSharp.Diffing.Core.Diffs;
+
 namespace AngleSharp.Diffing.Core;
 
 /// 
@@ -85,30 +87,30 @@ private IEnumerable CompareNode(in Comparison comparison)
         }
 
         var compareRes = _diffingStrategy.Compare(comparison);
-        if (compareRes.HasFlag(CompareResult.Different))
+        if (compareRes.Decision.HasFlag(CompareDecision.Different))
         {
-            IDiff diff = new NodeDiff(comparison);
-            return new[] { diff };
+            IDiff diff = compareRes.Diff ?? new NodeDiff(comparison);
+            return [diff];
         }
 
         return Array.Empty();
     }
 
-    private IEnumerable CompareElement(in Comparison comparison)
+    private List CompareElement(in Comparison comparison)
     {
         var result = new List();
 
         var compareRes = _diffingStrategy.Compare(comparison);
-        if (compareRes.HasFlag(CompareResult.Different))
+        if (compareRes.Decision.HasFlag(CompareDecision.Different))
         {
-            result.Add(new NodeDiff(comparison));
+            result.Add(compareRes.Diff ?? new ElementDiff(comparison, ElementDiffKind.Unspecified));
         }
 
-        if (!compareRes.HasFlag(CompareResult.Skip))
+        if (!compareRes.Decision.HasFlag(CompareDecision.Skip))
         {
-            if (!compareRes.HasFlag(CompareResult.SkipAttributes))
+            if (!compareRes.Decision.HasFlag(CompareDecision.SkipAttributes))
                 result.AddRange(CompareElementAttributes(comparison));
-            if (!compareRes.HasFlag(CompareResult.SkipChildren))
+            if (!compareRes.Decision.HasFlag(CompareDecision.SkipChildren))
                 result.AddRange(CompareChildNodes(comparison));
         }
 
@@ -184,8 +186,10 @@ private IEnumerable CompareAttributes(IEnumerable co
         foreach (var comparison in comparisons)
         {
             var compareRes = _diffingStrategy.Compare(comparison);
-            if (compareRes == CompareResult.Different)
-                yield return new AttrDiff(comparison);
+            if (compareRes.Decision == CompareDecision.Different)
+            {
+                yield return compareRes.Diff ?? new AttrDiff(comparison, AttrDiffKind.Unspecified);
+            }
         }
     }
 }
diff --git a/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs b/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs
new file mode 100644
index 0000000..4467e48
--- /dev/null
+++ b/src/AngleSharp.Diffing/Core/NodeTypeExtensions.cs
@@ -0,0 +1,22 @@
+namespace AngleSharp.Diffing.Core;
+
+/// 
+/// Helper methods for working with .
+/// 
+public static class NodeTypeExtensions
+{
+    /// 
+    /// Gets the diff target based on the node type.
+    /// 
+    /// Mode type to get the diff target off.
+    public static DiffTarget ToDiffTarget(this NodeType nodeType)
+    {
+        return nodeType switch
+        {
+            NodeType.Element => DiffTarget.Element,
+            NodeType.Comment => DiffTarget.Comment,
+            NodeType.Text => DiffTarget.Text,
+            _ => DiffTarget.Node
+        };
+    }
+}
diff --git a/src/AngleSharp.Diffing/Core/SourceMap.cs b/src/AngleSharp.Diffing/Core/SourceMap.cs
index 47e504d..4f99c8d 100644
--- a/src/AngleSharp.Diffing/Core/SourceMap.cs
+++ b/src/AngleSharp.Diffing/Core/SourceMap.cs
@@ -6,8 +6,8 @@ namespace AngleSharp.Diffing.Core;
 [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")]
 public class SourceMap : IEnumerable
 {
-    private readonly HashSet _matched = new HashSet();
-    private readonly Dictionary _sources = new Dictionary();
+    private readonly HashSet _matched = new(StringComparer.OrdinalIgnoreCase);
+    private readonly Dictionary _sources = new(StringComparer.OrdinalIgnoreCase);
 
     /// 
     /// Gets the type of the sources in the collection.
diff --git a/src/AngleSharp.Diffing/DiffBuilder.cs b/src/AngleSharp.Diffing/DiffBuilder.cs
index ee40b9e..bf94b6c 100644
--- a/src/AngleSharp.Diffing/DiffBuilder.cs
+++ b/src/AngleSharp.Diffing/DiffBuilder.cs
@@ -17,12 +17,12 @@ public class DiffBuilder
     /// 
     /// Gets or sets the control markup string.
     /// 
-    public string Control { get => _control; set => _control = value ?? throw new ArgumentNullException(nameof(Control)); }
+    public string Control { get => _control; set => _control = value ?? throw new ArgumentNullException(nameof(value)); }
 
     /// 
     /// Gets or sets the test markup string.
     /// 
-    public string Test { get => _test; set => _test = value ?? throw new ArgumentNullException(nameof(Test)); }
+    public string Test { get => _test; set => _test = value ?? throw new ArgumentNullException(nameof(value)); }
 
     private DiffBuilder(string control)
     {
diff --git a/src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs b/src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs
similarity index 95%
rename from src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs
rename to src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs
index 14ca9d5..2049656 100644
--- a/src/AngleSharp.Diffing/Extensions/NodeListExtensions.cs
+++ b/src/AngleSharp.Diffing/Extensions/AngleSharpDomExtensions.cs
@@ -34,5 +34,5 @@ public static IEnumerable ToComparisonSourceList(this IEnumera
     /// Creates a comparison source from a node.
     /// 
     public static ComparisonSource ToComparisonSource(this INode node, int index, ComparisonSourceType sourceType, string path = "")
-        => new ComparisonSource(node, index, path, sourceType);
+        => new(node, index, path, sourceType);
 }
diff --git a/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs b/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs
index be9c1ac..2cb0435 100644
--- a/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs
+++ b/src/AngleSharp.Diffing/Extensions/EmptyHtmlCollection.cs
@@ -8,10 +8,12 @@ public class EmptyHtmlCollection : IHtmlCollection where T : IElement
 {
     /// 
     [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
+    [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types")]
     public T this[int index] => throw new IndexOutOfRangeException();
 
     /// 
     [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
+    [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types")]
     public T this[string id] => throw new IndexOutOfRangeException();
 
     /// 
diff --git a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
index 014cf85..74e27d7 100644
--- a/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
+++ b/src/AngleSharp.Diffing/Extensions/UnexpectedDOMTreeStructureException.cs
@@ -1,6 +1,4 @@
-using System.Runtime.Serialization;
-
-namespace AngleSharp.Diffing.Extensions;
+namespace AngleSharp.Diffing.Extensions;
 
 /// 
 /// Represents an exception that is thrown when a part of the DOM tree is not as expected.
@@ -15,6 +13,17 @@ public sealed class UnexpectedDOMTreeStructureException : Exception
     public UnexpectedDOMTreeStructureException()
         : base("The DOM tree structure was not as expected by AngleSharp.Diffing.") { }
 
-    private UnexpectedDOMTreeStructureException(SerializationInfo info, StreamingContext context)
-        : base(info, context) { }
+    /// 
+    /// Creates an instance of the .
+    /// 
+    public UnexpectedDOMTreeStructureException(string message) : base(message)
+    {
+    }
+
+    /// 
+    /// Creates an instance of the .
+    /// 
+    public UnexpectedDOMTreeStructureException(string message, Exception innerException) : base(message, innerException)
+    {
+    }
 }
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
index 6c935c2..782773d 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/AttributeComparer.cs
@@ -17,7 +17,7 @@ public static class AttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         var (ignoreCase, isRegexValueCompare) = GetComparisonModifiers(comparison);
@@ -25,7 +25,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
         var hasSameName = CompareAttributeNames(comparison, ignoreCase, isRegexValueCompare);
 
         if (!hasSameName)
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
 
         var hasSameValue = isRegexValueCompare
             ? CompareAttributeValuesByRegex(comparison, ignoreCase)
@@ -33,7 +33,7 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
 
         return hasSameValue
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static (bool ignoreCase, bool isRegexCompare) GetComparisonModifiers(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
index 2658b63..feb77b2 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/BooleanAttributeComparer.cs
@@ -5,7 +5,7 @@
 /// 
 public class BooleanAttributeComparer
 {
-    private static readonly HashSet BooleanAttributesSet = new HashSet()
+    private static readonly HashSet BooleanAttributesSet = new(StringComparer.OrdinalIgnoreCase)
     {
         "allowfullscreen",
         "allowpaymentrequest",
@@ -18,7 +18,7 @@ public class BooleanAttributeComparer
         "defer",
         "disabled",
         "formnovalidate",
-        "hidden",
+        "inert",
         "ismap",
         "itemscope",
         "loop",
@@ -27,10 +27,14 @@ public class BooleanAttributeComparer
         "nomodule",
         "novalidate",
         "open",
+        "playsinline",
         "readonly",
         "required",
         "reversed",
         "selected",
+        "shadowrootclonable",
+        "shadowrootdelegatesfocus",
+        "shadowrootserializable",
         "typemustmatch"
     };
 
@@ -55,10 +59,10 @@ public BooleanAttributeComparer(BooleanAttributeComparision mode)
     /// 
     public CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
         if (!IsAttributeNamesEqual(comparison))
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
         if (!BooleanAttributesSet.Contains(comparison.Control.Attribute.Name))
             return currentDecision;
 
@@ -66,7 +70,9 @@ public CompareResult Compare(in AttributeComparison comparison, CompareResult cu
             ? CompareStrict(comparison)
             : true;
 
-        return hasSameValue ? CompareResult.Same : CompareResult.Different;
+        return hasSameValue ?
+            CompareResult.Same :
+            CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool IsAttributeNamesEqual(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
index 8f0d56e..5cd59c5 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/ClassAttributeComparer.cs
@@ -12,19 +12,19 @@ public static class ClassAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         if (!IsBothClassAttributes(comparison))
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name));
 
-        var (ctrlElm, testElm) = comparison.GetAttributeElements();
+        var (ctrlElm, testElm) = comparison.AttributeElements;
         if (ctrlElm.ClassList.Length != testElm.ClassList.Length)
-            return CompareResult.Different;
+            return CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
 
-        return ctrlElm.ClassList.All(x => testElm.ClassList.Contains(x))
+        return ctrlElm.ClassList.All(x => testElm.ClassList.Contains(x, StringComparer.Ordinal))
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool IsBothClassAttributes(in AttributeComparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
index bafb648..c74f619 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs
@@ -12,7 +12,7 @@ public static class IgnoreAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return comparison.Control.Attribute.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase)
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
index 5e3e73d..19fcb5e 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/OrderingStyleAttributeComparer.cs
@@ -10,7 +10,7 @@ public static class OrderingStyleAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return IsStyleAttributeComparison(comparison)
@@ -18,20 +18,20 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
             : currentDecision;
     }
 
+    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
+    {
+        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
+            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+    }
+
     private static CompareResult CompareElementStyle(in AttributeComparison comparison)
     {
-        var (ctrlElm, testElm) = comparison.GetAttributeElements();
+        var (ctrlElm, testElm) = comparison.AttributeElements;
         var ctrlStyle = ctrlElm.GetStyle();
         var testStyle = testElm.GetStyle();
         return CompareCssStyleDeclarations(ctrlStyle, testStyle)
             ? CompareResult.Same
-            : CompareResult.Different;
-    }
-
-    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
-    {
-        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
-            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, ICssStyleDeclaration test)
@@ -39,8 +39,8 @@ private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, IC
         if (control.Length != test.Length)
             return false;
 
-        var orderedControl = control.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x);
-        var orderedTest = test.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x);
+        var orderedControl = control.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x, StringComparer.Ordinal);
+        var orderedTest = test.CssText.Split(';').Select(x => x.Trim()).OrderBy(x => x, StringComparer.Ordinal);
 
         return orderedControl.SequenceEqual(orderedTest, StringComparer.Ordinal);
     }
diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
index 76ee635..8b41cdf 100644
--- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/StyleAttributeComparer.cs
@@ -10,7 +10,7 @@ public static class StyleAttributeComparer
     /// 
     public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         return IsStyleAttributeComparison(comparison)
@@ -18,20 +18,20 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe
             : currentDecision;
     }
 
+    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
+    {
+        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
+            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+    }
+
     private static CompareResult CompareElementStyle(in AttributeComparison comparison)
     {
-        var (ctrlElm, testElm) = comparison.GetAttributeElements();
+        var (ctrlElm, testElm) = comparison.AttributeElements;
         var ctrlStyle = ctrlElm.GetStyle();
         var testStyle = testElm.GetStyle();
         return CompareCssStyleDeclarations(ctrlStyle, testStyle)
             ? CompareResult.Same
-            : CompareResult.Different;
-    }
-
-    private static bool IsStyleAttributeComparison(in AttributeComparison comparison)
-    {
-        return comparison.Control.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal) &&
-            comparison.Test.Attribute.Name.Equals(AttributeNames.Style, StringComparison.Ordinal);
+            : CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value));
     }
 
     private static bool CompareCssStyleDeclarations(ICssStyleDeclaration control, ICssStyleDeclaration test)
diff --git a/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs b/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
index d06359e..7ea0bc5 100644
--- a/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/CommentStrategies/CommentComparer.cs
@@ -10,10 +10,14 @@ public static class CommentComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
+            return currentDecision;
+
+        if (comparison.TryGetNodesAsType(out var controlComment, out var testComment))
+            return controlComment.Data.Equals(testComment.Data, StringComparison.Ordinal)
+                ? CompareResult.Same
+                : CompareResult.FromDiff(new CommentDiff(comparison));
+        else
             return currentDecision;
-        return comparison.Control.Node.NodeType == NodeType.Comment && comparison.AreNodeTypesEqual
-            ? CompareResult.Same
-            : CompareResult.Different;
     }
 }
diff --git a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs
index 4856f0f..b4348c0 100644
--- a/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs
+++ b/src/AngleSharp.Diffing/Strategies/DiffingStrategyPipeline.cs
@@ -6,12 +6,12 @@ namespace AngleSharp.Diffing.Strategies;
 /// 
 public class DiffingStrategyPipeline : IDiffingStrategy, IDiffingStrategyCollection
 {
-    private readonly List> _nodeFilters = new List>();
-    private readonly List> _attrsFilters = new List>();
-    private readonly List> _nodeMatchers = new List>();
-    private readonly List> _attrsMatchers = new List>();
-    private readonly List> _nodeComparers = new List>();
-    private readonly List> _attrComparers = new List>();
+    private readonly List> _nodeFilters = new();
+    private readonly List> _attrsFilters = new();
+    private readonly List> _nodeMatchers = new();
+    private readonly List> _attrsMatchers = new();
+    private readonly List> _nodeComparers = new();
+    private readonly List> _attrComparers = new();
 
     /// 
     /// Gets whether the pipeline have any matchers registered.
@@ -64,7 +64,7 @@ public IEnumerable Match(IDiffContext context, SourceMap co
     public CompareResult Compare(in AttributeComparison comparison) => Compare(comparison, _attrComparers, CompareResult.Different);
 
     /// 
-    public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _nodeFilters.Add(filterStrategy);
@@ -74,7 +74,7 @@ public IDiffingStrategyCollection AddFilter(FilterStrategy fil
     }
 
     /// 
-    public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddFilter(FilterStrategy filterStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _attrsFilters.Add(filterStrategy);
@@ -84,7 +84,7 @@ public IDiffingStrategyCollection AddFilter(FilterStrategy
-    public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _nodeMatchers.Insert(0, matchStrategy);
@@ -94,7 +94,7 @@ public IDiffingStrategyCollection AddMatcher(MatchStrategy
-    public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddMatcher(MatchStrategy matchStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _attrsMatchers.Insert(0, matchStrategy);
@@ -104,7 +104,7 @@ public IDiffingStrategyCollection AddMatcher(MatchStrategy
-    public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _nodeComparers.Add(compareStrategy);
@@ -114,7 +114,7 @@ public IDiffingStrategyCollection AddComparer(CompareStrategy compar
     }
 
     /// 
-    public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType)
+    public IDiffingStrategyCollection AddComparer(CompareStrategy compareStrategy, StrategyType strategyType = StrategyType.Specialized)
     {
         if (strategyType == StrategyType.Specialized)
             _attrComparers.Add(compareStrategy);
@@ -123,7 +123,7 @@ public IDiffingStrategyCollection AddComparer(CompareStrategy(in T source, List> filterStrategies)
+    private static FilterDecision Filter(in T source, List> filterStrategies)
     {
         var result = FilterDecision.Keep;
         for (int i = 0; i < filterStrategies.Count; i++)
@@ -133,7 +133,7 @@ private FilterDecision Filter(in T source, List> filterStra
         return result;
     }
 
-    private CompareResult Compare(in TComparison comparison, List> compareStrategies, CompareResult initialResult)
+    private static CompareResult Compare(in TComparison comparison, List> compareStrategies, CompareResult initialResult)
     {
         var result = initialResult;
         foreach (var comparer in compareStrategies)
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs
index aaac7a6..390ccbc 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffMatchSelectorReturnedTooManyResultsException.cs
@@ -27,11 +27,4 @@ public DiffMatchSelectorReturnedTooManyResultsException(string message) : base(m
     public DiffMatchSelectorReturnedTooManyResultsException(string message, Exception innerException) : base(message, innerException)
     {
     }
-
-    /// 
-    /// Creates a .
-    /// 
-    protected DiffMatchSelectorReturnedTooManyResultsException(SerializationInfo info, StreamingContext context) : base(info, context)
-    {
-    }
 }
\ No newline at end of file
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
index ef85c93..4c5d12a 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/ElementComparer.cs
@@ -1,3 +1,4 @@
+using AngleSharp.Diffing.Core.Diffs;
 using AngleSharp.Html.Parser.Tokens;
 
 namespace AngleSharp.Diffing.Strategies.ElementStrategies;
@@ -30,24 +31,27 @@ public ElementComparer(bool enforceTagClosing)
     /// 
     public CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
-        var result = comparison.Control.Node.NodeType == NodeType.Element && comparison.AreNodeTypesEqual
+        if (!comparison.TryGetNodesAsType(out var controlElement, out var testElement))
+            return currentDecision;
+
+        var result = comparison.AreNodeTypesEqual
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.Name));
 
         if (EnforceTagClosing && result == CompareResult.Same)
         {
-            if (comparison.Test.Node is not IElement testElement || testElement.SourceReference is not HtmlTagToken testTag)
+            if (testElement.SourceReference is not HtmlTagToken testTag)
                 throw new InvalidOperationException("No source reference attached to test element, cannot determine element tag closing style.");
 
-            if (comparison.Control.Node is not IElement controlElement || controlElement.SourceReference is not HtmlTagToken controlTag)
+            if (controlElement.SourceReference is not HtmlTagToken controlTag)
                 throw new InvalidOperationException("No source reference attached to test element, cannot determine element tag closing style.");
 
             return testTag.IsSelfClosing == controlTag.IsSelfClosing
                 ? result
-                : CompareResult.Different;
+                : CompareResult.FromDiff(new ElementDiff(comparison, ElementDiffKind.ClosingStyle));
         }
 
         return result;
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
index 7bc25dd..a5cd052 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreAttributesElementComparer.cs
@@ -12,12 +12,20 @@ public static class IgnoreAttributesElementComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision == CompareResult.Skip)
+        if (currentDecision == CompareResult.Skip || currentDecision == CompareResult.SkipAttributes || currentDecision == CompareResult.SkipChildrenAndAttributes)
             return currentDecision;
 
-        return ControlHasTruthyIgnoreAttributesAttribute(comparison)
-            ? CompareResult.SkipAttributes
-            : currentDecision;
+        if (!ControlHasTruthyIgnoreAttributesAttribute(comparison))
+            return currentDecision;
+
+        return currentDecision.Decision switch
+        {
+            CompareDecision.None => CompareResult.SkipAttributes,
+            CompareDecision.Same => CompareResult.SkipAttributes,
+            CompareDecision.Different => CompareResult.SkipAttributes,
+            CompareDecision.SkipChildren => CompareResult.SkipChildrenAndAttributes,
+            _ => currentDecision,
+        };
     }
 
     private static bool ControlHasTruthyIgnoreAttributesAttribute(in Comparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
index cc19f1f..966fe7b 100644
--- a/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/ElementStrategies/IgnoreChildrenElementComparer.cs
@@ -12,12 +12,20 @@ public static class IgnoreChildrenElementComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision == CompareResult.Skip)
+        if (currentDecision == CompareResult.Skip || currentDecision == CompareResult.SkipChildren || currentDecision == CompareResult.SkipChildrenAndAttributes)
             return currentDecision;
 
-        return ControlHasTruthyIgnoreChildrenAttribute(comparison)
-            ? CompareResult.SkipChildren
-            : currentDecision;
+        if (!ControlHasTruthyIgnoreChildrenAttribute(comparison))
+            return currentDecision;
+
+        return currentDecision.Decision switch
+        {
+            CompareDecision.None => CompareResult.SkipChildren,
+            CompareDecision.Same => CompareResult.SkipChildren,
+            CompareDecision.Different => CompareResult.SkipChildren,
+            CompareDecision.SkipAttributes => CompareResult.SkipChildrenAndAttributes,
+            _ => currentDecision,
+        };
     }
 
     private static bool ControlHasTruthyIgnoreChildrenAttribute(in Comparison comparison)
diff --git a/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs b/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs
index 6a6768f..6da2532 100644
--- a/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs
+++ b/src/AngleSharp.Diffing/Strategies/IDiffingStrategyCollection.cs
@@ -3,6 +3,7 @@ namespace AngleSharp.Diffing.Strategies;
 /// 
 /// Represents a collection of diffing strategies.
 /// 
+[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Ignored.")]
 public interface IDiffingStrategyCollection
 {
     /// 
diff --git a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
index f8784e1..8284a2a 100644
--- a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/StyleSheetTextNodeComparer.cs
@@ -10,10 +10,10 @@ public static class StyleSheetTextNodeComparer
     /// 
     public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
         if (TryGetStyleDeclaretions(comparison, out var controlStyles, out var testStyles))
-            return Compare(controlStyles, testStyles);
+            return Compare(comparison, controlStyles, testStyles);
         else
             return currentDecision;
     }
@@ -36,13 +36,13 @@ private static bool TryGetStyleDeclaretions(in Comparison comparison, [NotNullWh
             return false;
     }
 
-    private static CompareResult Compare(IStyleSheet controlStyles, IStyleSheet testStyles)
+    private static CompareResult Compare(in Comparison comparison, IStyleSheet controlStyles, IStyleSheet testStyles)
     {
         var control = controlStyles.ToCss();
         var test = testStyles.ToCss();
 
         return control.Equals(test, StringComparison.Ordinal)
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new StylesheetDiff(comparison));
     }
 }
diff --git a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
index af458a4..9f6f5fe 100644
--- a/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
+++ b/src/AngleSharp.Diffing/Strategies/TextNodeStrategies/TextNodeComparer.cs
@@ -11,7 +11,7 @@ public class TextNodeComparer
     private const string WHITESPACE_ATTR_NAME = "diff:whitespace";
     private const string IGNORECASE_ATTR_NAME = "diff:ignorecase";
     private const string REGEX_ATTR_NAME = "diff:regex";
-    private static readonly Regex WhitespaceReplace = new Regex(@"\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(5));
+    private static readonly Regex WhitespaceReplace = new(@"\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(5));
 
     /// 
     /// Gets the whitespace option of the comparer instance.
@@ -37,16 +37,16 @@ public TextNodeComparer(WhitespaceOption option = WhitespaceOption.Preserve, boo
     /// 
     public CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
     {
-        if (currentDecision.IsSameOrSkip())
+        if (currentDecision.IsSameOrSkip)
             return currentDecision;
 
         if (comparison.TryGetNodesAsType(out var controlTextNode, out var testTextNode))
-            return Compare(controlTextNode, testTextNode);
+            return Compare(comparison, controlTextNode, testTextNode);
         else
             return currentDecision;
     }
 
-    private CompareResult Compare(IText controlTextNode, IText testTextNode)
+    private CompareResult Compare(in Comparison comparison, IText controlTextNode, IText testTextNode)
     {
         var option = GetWhitespaceOption(controlTextNode);
         var compareMethod = GetCompareMethod(controlTextNode);
@@ -68,11 +68,11 @@ private CompareResult Compare(IText controlTextNode, IText testTextNode)
         var isRegexCompare = GetIsRegexComparison(controlTextNode);
 
         return isRegexCompare
-            ? PerformRegexCompare(compareMethod, controlText, testText)
-            : PerformStringCompare(compareMethod, controlText, testText);
+            ? PerformRegexCompare(comparison, compareMethod, controlText, testText)
+            : PerformStringCompare(comparison, compareMethod, controlText, testText);
     }
 
-    private static CompareResult PerformRegexCompare(StringComparison compareMethod, string controlText, string testText)
+    private static CompareResult PerformRegexCompare(in Comparison comparison, StringComparison compareMethod, string controlText, string testText)
     {
         var regexOptions = compareMethod == StringComparison.OrdinalIgnoreCase
             ? RegexOptions.IgnoreCase
@@ -80,14 +80,14 @@ private static CompareResult PerformRegexCompare(StringComparison compareMethod,
 
         return Regex.IsMatch(testText, controlText, regexOptions, TimeSpan.FromSeconds(5))
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new TextDiff(comparison));
     }
 
-    private static CompareResult PerformStringCompare(StringComparison compareMethod, string controlText, string testText)
+    private static CompareResult PerformStringCompare(in Comparison comparison, StringComparison compareMethod, string controlText, string testText)
     {
         return controlText.Equals(testText, compareMethod)
             ? CompareResult.Same
-            : CompareResult.Different;
+            : CompareResult.FromDiff(new TextDiff(comparison));
     }
 
     private static bool GetIsRegexComparison(IText controlTextNode)
diff --git a/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs b/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs
new file mode 100644
index 0000000..6492a5e
--- /dev/null
+++ b/src/AngleSharp.Diffing/System.Runtime.CompilerServices/IsExternalInit.cs
@@ -0,0 +1,66 @@
+// 
+//   This code file has automatically been added by the "IsExternalInit" NuGet package (https://www.nuget.org/packages/IsExternalInit).
+//   Please see https://github.com/manuelroemer/IsExternalInit for more information.
+//
+//   IMPORTANT:
+//   DO NOT DELETE THIS FILE if you are using a "packages.config" file to manage your NuGet references.
+//   Consider migrating to PackageReferences instead:
+//   https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference
+//   Migrating brings the following benefits:
+//   * The "IsExternalInit" folder and the "IsExternalInit.cs" file don't appear in your project.
+//   * The added file is immutable and can therefore not be modified by coincidence.
+//   * Updating/Uninstalling the package will work flawlessly.
+// 
+
+#region License
+// MIT License
+// 
+// Copyright (c) Manuel Römer
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#endregion
+
+#if !ISEXTERNALINIT_DISABLE
+#nullable enable
+#pragma warning disable
+
+namespace System.Runtime.CompilerServices
+{
+    using global::System.Diagnostics;
+    using global::System.Diagnostics.CodeAnalysis;
+
+    /// 
+    ///     Reserved to be used by the compiler for tracking metadata.
+    ///     This class should not be used by developers in source code.
+    /// 
+    /// 
+    ///     This definition is provided by the IsExternalInit NuGet package (https://www.nuget.org/packages/IsExternalInit).
+    ///     Please see https://github.com/manuelroemer/IsExternalInit for more information.
+    /// 
+#if !ISEXTERNALINIT_INCLUDE_IN_CODE_COVERAGE
+    [ExcludeFromCodeCoverage, DebuggerNonUserCode]
+#endif
+    internal static class IsExternalInit
+    {
+    }
+}
+
+#pragma warning restore
+#nullable restore
+#endif // ISEXTERNALINIT_DISABLE
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ad2c443..bbbcb7b 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,19 +1,26 @@
 
-  
-    0.18.1
-  
+	
+		1.0.0
+	
 
-  
-    enable
-    11.0
-    enable
-    CS8600;CS8602;CS8603;CS8625
-    true
-    ../Key.snk
-  
+	
+		enable
+		latest
+		enable
+		CS8600;CS8602;CS8603;CS8625
+		true
+		../Key.snk
+	
 
-  
-    
-    
-  
+	
+		AllEnabledByDefault
+		true
+		latest
+		true
+	
+
+	
+		
+		
+	
 
\ No newline at end of file