Skip to content

Commit 7a0c5e1

Browse files
SebastianStehleegil
andcommitted
feat: changed CompareStrategy to return CompareResult
* Ensure that the correct diff is returned. * Use records. * Move kind to beginning of text. * Fix compilation error and test for custom diff. * Update src/AngleSharp.Diffing/Core/CompareResult.cs Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/AngleSharp.Diffing/Core/Comparison.cs Co-authored-by: Egil Hansen <egil@assimilated.dk> * Update src/AngleSharp.Diffing/Core/AttributeComparison.cs Co-authored-by: Egil Hansen <egil@assimilated.dk> * 1. Rename CompareResultDecision 2. Move method inside type. * Rename method. * Get rid of StylesOrder * More records. * refactor: move IsExternalInit to folder matching namespace * add comment to explain use of ReferenceEquals * Move types into two files * simplify attrdiff object hierarchi * remove diff type check * clean up using statements * refactor: IsSameOrSkip as property * refactor: normalize tests * feat: add CommentComparer tests * nodes do not have different closing tags, elements do, so switch to using ElementDiff when comparing elements * fix: override ComparisonSource.ToString for better output * fix: use unspecified when no diff is returned * override ToString in AttributeComparisonSource * bump version number --------- Co-authored-by: Egil Hansen <egil@assimilated.dk>
1 parent e514267 commit 7a0c5e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+688
-345
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 0.18.2
2+
3+
- 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).
4+
- Changed `ElementComparer` to skip comparing two nodes of different types. By [@SebastianStehle](https://github.com/SebastianStehle).
5+
16
# 0.18.1
27

38
- Fixed element comparer such that it can strictly check if the closing tags in the source markup is the same.

src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs

+90-19
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,46 @@ public void WhenNodesAreDifferentADiffIsReturned()
103103
var results = sut.Compare(nodes, nodes).ToList();
104104

105105
results.Count.ShouldBe(3);
106-
results[0].ShouldBeOfType<NodeDiff>().ShouldSatisfyAllConditions(
106+
results[0].ShouldBeAssignableTo<NodeDiff>().ShouldSatisfyAllConditions(
107107
diff => diff.Control.Node.NodeName.ShouldBe("P"),
108108
diff => diff.Result.ShouldBe(DiffResult.Different),
109109
diff => diff.Target.ShouldBe(DiffTarget.Element)
110110
);
111-
results[1].ShouldBeOfType<NodeDiff>().ShouldSatisfyAllConditions(
111+
results[1].ShouldBeAssignableTo<NodeDiff>().ShouldSatisfyAllConditions(
112112
diff => diff.Control.Node.NodeName.ShouldBe("#comment"),
113113
diff => diff.Result.ShouldBe(DiffResult.Different),
114114
diff => diff.Target.ShouldBe(DiffTarget.Comment)
115115
);
116-
results[2].ShouldBeOfType<NodeDiff>().ShouldSatisfyAllConditions(
116+
results[2].ShouldBeAssignableTo<NodeDiff>().ShouldSatisfyAllConditions(
117+
diff => diff.Control.Node.NodeName.ShouldBe("#text"),
118+
diff => diff.Result.ShouldBe(DiffResult.Different),
119+
diff => diff.Target.ShouldBe(DiffTarget.Text)
120+
);
121+
}
122+
123+
[Fact(DisplayName = "When matched control/test nodes are different, a custom diff is returned")]
124+
public void WhenNodesAreDifferentADiffIsReturnedWithCustomDiff()
125+
{
126+
var nodes = ToNodeList("<p></p><!--comment-->textnode");
127+
var sut = CreateHtmlDiffer(
128+
nodeMatcher: OneToOneNodeListMatcher,
129+
nodeFilter: NoneNodeFilter,
130+
nodeComparer: DiffResultCustomNodeComparer);
131+
132+
var results = sut.Compare(nodes, nodes).ToList();
133+
134+
results.Count.ShouldBe(3);
135+
results[0].ShouldBeOfType<CustomNodeDiff>().ShouldSatisfyAllConditions(
136+
diff => diff.Control.Node.NodeName.ShouldBe("P"),
137+
diff => diff.Result.ShouldBe(DiffResult.Different),
138+
diff => diff.Target.ShouldBe(DiffTarget.Element)
139+
);
140+
results[1].ShouldBeOfType<CustomNodeDiff>().ShouldSatisfyAllConditions(
141+
diff => diff.Control.Node.NodeName.ShouldBe("#comment"),
142+
diff => diff.Result.ShouldBe(DiffResult.Different),
143+
diff => diff.Target.ShouldBe(DiffTarget.Comment)
144+
);
145+
results[2].ShouldBeOfType<CustomNodeDiff>().ShouldSatisfyAllConditions(
117146
diff => diff.Control.Node.NodeName.ShouldBe("#text"),
118147
diff => diff.Result.ShouldBe(DiffResult.Different),
119148
diff => diff.Target.ShouldBe(DiffTarget.Text)
@@ -237,6 +266,30 @@ public void WhenMatchedAttrsAreDiffAttrDiffIsReturned()
237266
);
238267
}
239268

269+
[Fact(DisplayName = "When matched control/test attributes are different, a diff is returned with custom diff")]
270+
public void WhenMatchedAttrsAreDiffAttrDiffIsReturnedWithCustomDiff()
271+
{
272+
var nodes = ToNodeList(@"<p id=""foo""></p>");
273+
274+
var sut = CreateHtmlDiffer(
275+
nodeMatcher: OneToOneNodeListMatcher,
276+
nodeFilter: NoneNodeFilter,
277+
nodeComparer: SameResultNodeComparer,
278+
attrMatcher: AttributeNameMatcher,
279+
attrFilter: NoneAttrFilter,
280+
attrComparer: DiffResultCustomAttrComparer);
281+
282+
var results = sut.Compare(nodes, nodes).ToList();
283+
284+
results.Count.ShouldBe(1);
285+
results[0].ShouldBeOfType<CustomAttrDiff>().ShouldSatisfyAllConditions(
286+
diff => diff.Control.Attribute.Name.ShouldBe("id"),
287+
diff => diff.Test.Attribute.Name.ShouldBe("id"),
288+
diff => diff.Result.ShouldBe(DiffResult.Different),
289+
diff => diff.Target.ShouldBe(DiffTarget.Attribute)
290+
);
291+
}
292+
240293
[Fact(DisplayName = "When matched control/test attributes are the same, no diffs are returned")]
241294
public void WhenMatchedAttrsAreSameNoDiffIsReturned()
242295
{
@@ -268,11 +321,11 @@ public void WhenBothTestAndControlHaveChildNodesTheseAreCompared()
268321
var results = sut.Compare(nodes, nodes).ToList();
269322

270323
results.Count.ShouldBe(5);
271-
results[0].ShouldBeOfType<NodeDiff>().Control.Node.NodeName.ShouldBe("MAIN");
272-
results[1].ShouldBeOfType<NodeDiff>().Control.Node.NodeName.ShouldBe("H1");
273-
results[2].ShouldBeOfType<NodeDiff>().Control.Node.NodeValue.ShouldBe("foobar");
274-
results[3].ShouldBeOfType<NodeDiff>().Control.Node.NodeName.ShouldBe("P");
275-
results[4].ShouldBeOfType<NodeDiff>().Control.Node.NodeName.ShouldBe("#text");
324+
results[0].ShouldBeAssignableTo<NodeDiff>().Control.Node.NodeName.ShouldBe("MAIN");
325+
results[1].ShouldBeAssignableTo<NodeDiff>().Control.Node.NodeName.ShouldBe("H1");
326+
results[2].ShouldBeAssignableTo<NodeDiff>().Control.Node.NodeValue.ShouldBe("foobar");
327+
results[3].ShouldBeAssignableTo<NodeDiff>().Control.Node.NodeName.ShouldBe("P");
328+
results[4].ShouldBeAssignableTo<NodeDiff>().Control.Node.NodeName.ShouldBe("#text");
276329
}
277330

278331
[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
288341
var results = sut.Compare(ToNodeList(control), ToNodeList(test)).ToList();
289342

290343
results.Count.ShouldBe(2);
291-
results[0].ShouldBeOfType<NodeDiff>();
344+
results[0].ShouldBeAssignableTo<NodeDiff>();
292345
results[1].ShouldBeOfType(expectedDiffType);
293346
}
294347

@@ -309,8 +362,8 @@ public void ComparisonSourcesHaveCorrectType()
309362

310363
results.Count.ShouldBe(2);
311364

312-
results[0].ShouldBeOfType<NodeDiff>().Control.SourceType.ShouldBe(ComparisonSourceType.Control);
313-
results[0].ShouldBeOfType<NodeDiff>().Test.SourceType.ShouldBe(ComparisonSourceType.Test);
365+
results[0].ShouldBeAssignableTo<NodeDiff>().Control.SourceType.ShouldBe(ComparisonSourceType.Control);
366+
results[0].ShouldBeAssignableTo<NodeDiff>().Test.SourceType.ShouldBe(ComparisonSourceType.Test);
314367
results[1].ShouldBeOfType<AttrDiff>().Control.SourceType.ShouldBe(ComparisonSourceType.Control);
315368
results[1].ShouldBeOfType<AttrDiff>().Test.SourceType.ShouldBe(ComparisonSourceType.Test);
316369
}
@@ -349,14 +402,14 @@ public void Test2()
349402
}
350403

351404
[Theory(DisplayName = "When comparer returns SkipChildren flag from an element comparison, child nodes are not compared")]
352-
[InlineData(CompareResult.Same | CompareResult.SkipChildren)]
353-
[InlineData(CompareResult.Skip | CompareResult.SkipChildren)]
354-
public void Test3(CompareResult compareResult)
405+
[InlineData(CompareDecision.Same | CompareDecision.SkipChildren)]
406+
[InlineData(CompareDecision.Skip | CompareDecision.SkipChildren)]
407+
public void Test3(CompareDecision decision)
355408
{
356409
var sut = CreateHtmlDiffer(
357410
nodeMatcher: OneToOneNodeListMatcher,
358411
nodeFilter: NoneNodeFilter,
359-
nodeComparer: c => c.Control.Node.NodeName == "P" ? compareResult : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"),
412+
nodeComparer: c => c.Control.Node.NodeName == "P" ? new CompareResult(decision) : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"),
360413
attrMatcher: AttributeNameMatcher,
361414
attrFilter: NoneAttrFilter,
362415
attrComparer: SameResultAttrComparer
@@ -368,14 +421,14 @@ public void Test3(CompareResult compareResult)
368421
}
369422

370423
[Theory(DisplayName = "When comparer returns SkipAttributes flag from an element comparison, attributes are not compared")]
371-
[InlineData(CompareResult.Same | CompareResult.SkipAttributes)]
372-
[InlineData(CompareResult.Skip | CompareResult.SkipAttributes)]
373-
public void Test4(CompareResult compareResult)
424+
[InlineData(CompareDecision.Same | CompareDecision.SkipAttributes)]
425+
[InlineData(CompareDecision.Skip | CompareDecision.SkipAttributes)]
426+
public void Test4(CompareDecision decision)
374427
{
375428
var sut = CreateHtmlDiffer(
376429
nodeMatcher: OneToOneNodeListMatcher,
377430
nodeFilter: NoneNodeFilter,
378-
nodeComparer: c => compareResult,
431+
nodeComparer: c => new CompareResult(decision),
379432
attrMatcher: AttributeNameMatcher,
380433
attrFilter: NoneAttrFilter,
381434
attrComparer: SameResultAttrComparer
@@ -411,6 +464,7 @@ private static IEnumerable<Comparison> OneToOneNodeListMatcher(
411464
#region NodeComparers
412465
private static CompareResult SameResultNodeComparer(Comparison comparison) => CompareResult.Same;
413466
private static CompareResult DiffResultNodeComparer(Comparison comparison) => CompareResult.Different;
467+
private static CompareResult DiffResultCustomNodeComparer(Comparison comparison) => CompareResult.FromDiff(new CustomNodeDiff(comparison));
414468
#endregion
415469

416470
#region AttributeMatchers
@@ -452,5 +506,22 @@ private static Func<AttributeComparisonSource, FilterDecision> SpecificAttrFilte
452506
#region AttributeComparers
453507
public static CompareResult SameResultAttrComparer(AttributeComparison comparison) => CompareResult.Same;
454508
public static CompareResult DiffResultAttrComparer(AttributeComparison comparison) => CompareResult.Different;
509+
public static CompareResult DiffResultCustomAttrComparer(AttributeComparison comparison) => CompareResult.FromDiff(new CustomAttrDiff(comparison));
510+
#endregion
511+
512+
#region CustomDiff
513+
public record CustomNodeDiff : NodeDiff
514+
{
515+
public CustomNodeDiff(in Comparison comparison) : base(comparison)
516+
{
517+
}
518+
}
519+
520+
public record CustomAttrDiff : AttrDiff
521+
{
522+
public CustomAttrDiff(in AttributeComparison comparison) : base(comparison, AttrDiffKind.Unspecified)
523+
{
524+
}
525+
}
455526
#endregion
456527
}

src/AngleSharp.Diffing.Tests/DiffingTestBase.cs

+6
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,10 @@ protected SourceMap ToSourceMap(string html, ComparisonSourceType sourceType = C
6868
var source = ToComparisonSource(html, sourceType);
6969
return new SourceMap(source);
7070
}
71+
72+
public static TheoryData<CompareResult> SameAndSkipCompareResult = new TheoryData<CompareResult>
73+
{
74+
CompareResult.Same,
75+
CompareResult.Skip,
76+
};
7177
}

src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/AttributeComparerTest.cs

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
namespace AngleSharp.Diffing.Strategies.AttributeStrategies;
22

3-
43
public class AttributeComparerTest : DiffingTestBase
54
{
65
public AttributeComparerTest(DiffingTestFixture fixture) : base(fixture)
76
{
87
}
98

10-
[Fact(DisplayName = "When compare is called with a current decision of Same or Skip, the current decision is returned")]
11-
public void Test001()
9+
[Theory(DisplayName = "When current result is same or skip, the current decision is returned")]
10+
[MemberData(nameof(SameAndSkipCompareResult))]
11+
public void Test001(CompareResult currentResult)
1212
{
1313
var comparison = ToAttributeComparison(@"<b foo>", "foo",
14-
"<b bar>", "bar");
14+
"<b bar>", "bar");
1515

16-
AttributeComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same);
17-
AttributeComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
16+
new BooleanAttributeComparer(BooleanAttributeComparision.Strict)
17+
.Compare(comparison, currentResult)
18+
.ShouldBe(currentResult);
1819
}
1920

2021
[Fact(DisplayName = "When two attributes has the same name and no value, the compare result is Same")]
@@ -23,7 +24,9 @@ public void Test002()
2324
var comparison = ToAttributeComparison(@"<b foo>", "foo",
2425
"<b foo>", "foo");
2526

26-
AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same);
27+
AttributeComparer
28+
.Compare(comparison, CompareResult.Unknown)
29+
.ShouldBe(CompareResult.Same);
2730
}
2831

2932
[Fact(DisplayName = "When two attributes does not have the same name, the compare result is Different")]
@@ -32,7 +35,9 @@ public void Test003()
3235
var comparison = ToAttributeComparison(@"<b foo>", "foo",
3336
"<b bar>", "bar");
3437

35-
AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
38+
AttributeComparer
39+
.Compare(comparison, CompareResult.Unknown)
40+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name)));
3641
}
3742

3843
[Fact(DisplayName = "When two attribute values are the same, the compare result is Same")]
@@ -41,7 +46,9 @@ public void Test004()
4146
var comparison = ToAttributeComparison(@"<b foo=""bar"">", "foo",
4247
@"<b foo=""bar"">", "foo");
4348

44-
AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same);
49+
AttributeComparer
50+
.Compare(comparison, CompareResult.Unknown)
51+
.ShouldBe(CompareResult.Same);
4552
}
4653

4754
[Fact(DisplayName = "When two attribute values are different, the compare result is Different")]
@@ -50,7 +57,9 @@ public void Test005()
5057
var comparison = ToAttributeComparison(@"<b foo=""bar"">", "foo",
5158
@"<b foo=""baz"">", "foo");
5259

53-
AttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
60+
AttributeComparer
61+
.Compare(comparison, CompareResult.Unknown)
62+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)));
5463
}
5564

5665
[Fact(DisplayName = "When the control attribute is postfixed with :ignoreCase, " +

src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/BooleanAttributeComparerTest.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,25 @@ public BooleanAttributeComparerTest(DiffingTestFixture fixture) : base(fixture)
88
{
99
}
1010

11+
[Theory(DisplayName = "When current result is same or skip, the current decision is returned")]
12+
[MemberData(nameof(SameAndSkipCompareResult))]
13+
public void Test000(CompareResult currentResult)
14+
{
15+
var comparison = ToAttributeComparison(@"<b allowfullscreen=""false"">", "allowfullscreen", @"<b allowfullscreen>", "allowfullscreen");
16+
17+
new BooleanAttributeComparer(BooleanAttributeComparision.Strict)
18+
.Compare(comparison, currentResult)
19+
.ShouldBe(currentResult);
20+
}
21+
1122
[Fact(DisplayName = "When attribute names are not the same comparer returns different")]
1223
public void Test001()
1324
{
1425
var sut = new BooleanAttributeComparer(BooleanAttributeComparision.Strict);
1526
var comparison = ToAttributeComparison("<b foo>", "foo", "<b bar>", "bar");
1627

17-
sut.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
28+
sut.Compare(comparison, CompareResult.Unknown)
29+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name)));
1830
}
1931

2032
[Fact(DisplayName = "When attribute name is not an boolean attribute, its current result is returned")]

src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/ClassAttributeComparerTest.cs

+24-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ public ClassAttributeComparerTest(DiffingTestFixture fixture) : base(fixture)
66
{
77
}
88

9+
[Theory(DisplayName = "When current result is same or skip, the current decision is returned")]
10+
[MemberData(nameof(SameAndSkipCompareResult))]
11+
public void Test000(CompareResult currentResult)
12+
{
13+
var comparison = ToAttributeComparison($@"<p class=""foo"">", "class",
14+
$@"<p class=""foo"">", "class");
15+
16+
ClassAttributeComparer
17+
.Compare(comparison, currentResult)
18+
.ShouldBe(currentResult);
19+
}
20+
921
[Theory(DisplayName = "When a class attribute is compared, the order of individual " +
1022
"classes and multiple whitespace is ignored")]
1123
[InlineData("", "")]
@@ -18,7 +30,9 @@ public void Test009(string controlClasses, string testClasses)
1830
var comparison = ToAttributeComparison($@"<p class=""{controlClasses}"">", "class",
1931
$@"<p class=""{testClasses}"">", "class");
2032

21-
ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Same);
33+
ClassAttributeComparer
34+
.Compare(comparison, CompareResult.Unknown)
35+
.ShouldBe(CompareResult.Same);
2236
}
2337

2438
[Fact(DisplayName = "When a class attribute is matched up with another attribute, the result is different")]
@@ -27,7 +41,9 @@ public void Test010()
2741
var comparison = ToAttributeComparison(@"<p class=""foo"">", "class",
2842
@"<p bar=""bar"">", "bar");
2943

30-
ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
44+
ClassAttributeComparer
45+
.Compare(comparison, CompareResult.Unknown)
46+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Name)));
3147
}
3248

3349
[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)
3854
var comparison = ToAttributeComparison($@"<p class=""{controlClasses}"">", "class",
3955
$@"<p class=""{testClasses}"">", "class");
4056

41-
ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
57+
ClassAttributeComparer
58+
.Compare(comparison, CompareResult.Unknown)
59+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)));
4260
}
4361

4462
[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)
5169
var comparison = ToAttributeComparison($@"<p class=""{controlClasses}"">", "class",
5270
$@"<p class=""{testClasses}"">", "class");
5371

54-
ClassAttributeComparer.Compare(comparison, CompareResult.Unknown).ShouldBe(CompareResult.Different);
72+
ClassAttributeComparer
73+
.Compare(comparison, CompareResult.Unknown)
74+
.ShouldBe(CompareResult.FromDiff(new AttrDiff(comparison, AttrDiffKind.Value)));
5575
}
5676
}

src/AngleSharp.Diffing.Tests/Strategies/AttributeStrategies/IgnoreAttributeComparerTest.cs

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ public IgnoreAttributeComparerTest(DiffingTestFixture fixture) : base(fixture)
88
{
99
}
1010

11+
[Theory(DisplayName = "When current result is same or skip, the current decision is returned")]
12+
[MemberData(nameof(SameAndSkipCompareResult))]
13+
public void Test000(CompareResult currentResult)
14+
{
15+
var comparison = ToAttributeComparison(
16+
@"<p foo=""bar""></p>", "foo",
17+
@"<p foo=""bar""></p>", "foo"
18+
);
19+
20+
IgnoreAttributeComparer
21+
.Compare(comparison, currentResult)
22+
.ShouldBe(currentResult);
23+
}
24+
1125
[Fact(DisplayName = "When a attribute does not contain have the ':ignore' postfix, the current decision is returned")]
1226
public void Test003()
1327
{

0 commit comments

Comments
 (0)