diff --git a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs index 9769990..e9ad09d 100644 --- a/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs +++ b/src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs @@ -1,3 +1,8 @@ +using AngleSharp.Diffing.Strategies; +using AngleSharp.Diffing.Strategies.AttributeStrategies; +using AngleSharp.Diffing.Strategies.TextNodeStrategies; +using AngleSharp.Diffing.TestData; + namespace AngleSharp.Diffing; @@ -116,4 +121,38 @@ public void Test006(string control, string test) diffs.ShouldBeEmpty(); } -} + + [Theory(DisplayName = + "When a control element has ':ignore', elements with and without that attribute should return empty diffs")] + [MemberData(nameof(IgnoreAttributeTestData.ControlAndHtmlData), MemberType = typeof(IgnoreAttributeTestData))] + public void Test007(string controlHtml, string testHtml) + { + var diffs = DiffBuilder.Compare(controlHtml).WithTest(testHtml).Build(); + Assert.Empty(diffs); + } + + [Theory(DisplayName = + "When a control element has ':ignore', but IgnoreAttributeComparer is not active, diffs should be found")] + [MemberData(nameof(IgnoreAttributeTestData.ControlHtmlAndDiffData), MemberType = typeof(IgnoreAttributeTestData))] + public void Test008(string controlHtml, string testHtml, DiffResult expectedDiffResult) + { + var diffs = DiffBuilder + .Compare(controlHtml) + .WithTest(testHtml) + .WithOptions(a => a // Most important thing to note here is we do not have a ignore attribute comparer + .AddSearchingNodeMatcher() + .AddMatcher(AttributeNameMatcher.Match, StrategyType.Generalized) + .AddElementComparer(enforceTagClosing: false) + .AddMatcher(PostfixedAttributeMatcher.Match, StrategyType.Specialized) + .AddComparer(AttributeComparer.Compare, StrategyType.Generalized) + .AddClassAttributeComparer() + .AddBooleanAttributeComparer(BooleanAttributeComparision.Strict) + .AddStyleAttributeComparer()) + .Build() + .ToList(); + + Assert.Single(diffs); + Assert.Equal(DiffTarget.Attribute, diffs[0].Target); + Assert.Equal(expectedDiffResult, diffs[0].Result); + } +} \ No newline at end of file diff --git a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs index a59efc3..34bb922 100644 --- a/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs +++ b/src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs @@ -17,7 +17,7 @@ public void Test000(CompareResult currentResult) @"

", "foo" ); - IgnoreAttributeComparer + IgnoreAttributeStrategy .Compare(comparison, currentResult) .ShouldBe(currentResult); } @@ -30,8 +30,8 @@ public void Test003() @"

", "foo" ); - IgnoreAttributeComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); - IgnoreAttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); + IgnoreAttributeStrategy.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different); + IgnoreAttributeStrategy.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same); } [Fact(DisplayName = "When a attribute does contain have the ':ignore' postfix, Same is returned")] @@ -42,6 +42,6 @@ public void Test004() @"

", "foo" ); - IgnoreAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); + IgnoreAttributeStrategy.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same); } } diff --git a/src/AngleSharp.Diffing.Tests/TestData/IgnoreAttributeTestData.cs b/src/AngleSharp.Diffing.Tests/TestData/IgnoreAttributeTestData.cs new file mode 100644 index 0000000..daded07 --- /dev/null +++ b/src/AngleSharp.Diffing.Tests/TestData/IgnoreAttributeTestData.cs @@ -0,0 +1,45 @@ +namespace AngleSharp.Diffing.TestData; + +internal static class IgnoreAttributeTestData +{ + public static TheoryData ControlAndHtmlData() + { + var theoryData = new TheoryData(); + foreach (var (controlHtml, expectedHtml, _) in TestCases) + { + theoryData.Add(controlHtml, expectedHtml); + } + + return theoryData; + } + + public static TheoryData ControlHtmlAndDiffData() + { + var theoryData = new TheoryData(); + foreach (var (controlHtml, expectedHtml, expectedDiffResult) in TestCases) + { + theoryData.Add(controlHtml, expectedHtml, expectedDiffResult); + } + + return theoryData; + } + + private static readonly IEnumerable<(string controlHtml, string expectedHtml, DiffResult expectedDiffResult)> + TestCases = + [ + ("
", "
", DiffResult.Different), + ("
", "
", DiffResult.Different), + ("
", "
", DiffResult.Different), + ("
", "
", DiffResult.Missing), + ("", "", DiffResult.Different), + ("", "", DiffResult.Different), + ("", "", DiffResult.Different), + ("", "", DiffResult.Missing), + ("", "", DiffResult.Different), + ("", "", DiffResult.Missing), + ("", "", DiffResult.Different), + ("", "", DiffResult.Missing), + ("", "", DiffResult.Different), + ("", "", DiffResult.Missing), + ]; +} \ No newline at end of file diff --git a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs index 575ca96..35734aa 100644 --- a/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs +++ b/src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs @@ -1,4 +1,5 @@ using AngleSharp.Diffing.Core.Diffs; +using AngleSharp.Diffing.Strategies.AttributeStrategies; namespace AngleSharp.Diffing.Core; diff --git a/src/AngleSharp.Diffing/Core/SourceMap.cs b/src/AngleSharp.Diffing/Core/SourceMap.cs index 4f99c8d..4b30d12 100644 --- a/src/AngleSharp.Diffing/Core/SourceMap.cs +++ b/src/AngleSharp.Diffing/Core/SourceMap.cs @@ -70,7 +70,7 @@ public IEnumerable GetUnmatched() { foreach (var source in _sources.Values) { - if (!_matched.Contains(source.Attribute.Name)) + if (IsUnmatched(source.Attribute.Name)) yield return source; } } diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/DiffingStrategyPipelineBuilderExtensions.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/DiffingStrategyPipelineBuilderExtensions.cs index 17b8223..ca9f18d 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/DiffingStrategyPipelineBuilderExtensions.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/DiffingStrategyPipelineBuilderExtensions.cs @@ -23,6 +23,7 @@ public static IDiffingStrategyCollection IgnoreDiffAttributes(this IDiffingStrat public static IDiffingStrategyCollection AddAttributeNameMatcher(this IDiffingStrategyCollection builder) { builder.AddMatcher(AttributeNameMatcher.Match, StrategyType.Generalized); + builder.AddMatcher(IgnoreAttributeStrategy.Match, StrategyType.Generalized); return builder; } @@ -33,7 +34,7 @@ public static IDiffingStrategyCollection AddAttributeComparer(this IDiffingStrat { builder.AddMatcher(PostfixedAttributeMatcher.Match, StrategyType.Specialized); builder.AddComparer(AttributeComparer.Compare, StrategyType.Generalized); - builder.AddComparer(IgnoreAttributeComparer.Compare, StrategyType.Specialized); + builder.AddComparer(IgnoreAttributeStrategy.Compare, StrategyType.Specialized); return builder; } diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs index c74f619..eb42457 100644 --- a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeComparer.cs @@ -3,6 +3,7 @@ /// /// Represents the ignore attribute comparer. /// +[Obsolete("Has been moved to IgnoreAttributeStrategy")] public static class IgnoreAttributeComparer { private const string DIFF_IGNORE_POSTFIX = ":ignore"; @@ -15,8 +16,13 @@ public static CompareResult Compare(in AttributeComparison comparison, CompareRe if (currentDecision.IsSameOrSkip) return currentDecision; - return comparison.Control.Attribute.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase) + return IsIgnoreAttribute(comparison.Control.Attribute) ? CompareResult.Same : currentDecision; } -} + + private static bool IsIgnoreAttribute(IAttr source) + { + return source.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file diff --git a/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeStrategy.cs b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeStrategy.cs new file mode 100644 index 0000000..84d24b6 --- /dev/null +++ b/src/AngleSharp.Diffing/Strategies/AttributeStrategies/IgnoreAttributeStrategy.cs @@ -0,0 +1,47 @@ +namespace AngleSharp.Diffing.Strategies.AttributeStrategies; + +/// +/// Ignore Attribute matcher strategy. +/// +public static class IgnoreAttributeStrategy +{ + private const string DIFF_IGNORE_POSTFIX = ":ignore"; + + /// + /// The ignore attribute comparer. + /// + public static CompareResult Compare(in AttributeComparison comparison, CompareResult currentDecision) + { + if (currentDecision.IsSameOrSkip) + return currentDecision; + + return IsIgnoreAttribute(comparison.Control.Attribute) + ? CompareResult.Same + : currentDecision; + } + + /// + /// Attribute name matcher strategy. + /// + public static IEnumerable Match(IDiffContext context, SourceMap controlSources, SourceMap testSources) + { + if (controlSources is null) + throw new ArgumentNullException(nameof(controlSources)); + if (testSources is null) + throw new ArgumentNullException(nameof(testSources)); + + foreach (var control in controlSources.GetUnmatched()) + { + // An unmatched :ignore attribute can just be matched with itself if it isn't + // matched with a "test" attribute of the same name already. + // this means an ignored attribute is ignored even if it does not appear in the test html. + if (control.Attribute.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase)) + yield return new AttributeComparison(control, control); + } + } + + private static bool IsIgnoreAttribute(IAttr source) + { + return source.Name.EndsWith(DIFF_IGNORE_POSTFIX, StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file