Skip to content

Commit 12271e3

Browse files
committed
test: add comprehensive bUnit test suite (38 tests)
- Add tests/BlazorTextDiff.Tests project with bUnit 1.36.0 and xUnit 2.9.2 - DiffStatsTests: 10 tests covering constructor, both-pane counting, word-level aggregation, null/empty handling - TextDiffLineTests: 8 tests covering unchanged/inserted/deleted rendering, word+character span generation, imaginary piece skipping, null/empty - TextDiffPaneTests: 7 tests covering pane position classes, table structure, line numbers, nbsp for null positions, CSS class mapping - TextDiffTests: 13 tests covering identical/different text, null/empty, collapse behavior, IgnoreCase, IgnoreWhiteSpace, header rendering, multi-line text - All 38 tests passing
1 parent 44e7c49 commit 12271e3

File tree

6 files changed

+705
-0
lines changed

6 files changed

+705
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="bunit" Version="1.36.0" />
12+
<PackageReference Include="coverlet.collector" Version="6.0.2" />
13+
<PackageReference Include="DiffPlex" Version="1.9.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
15+
<PackageReference Include="xunit" Version="2.9.2" />
16+
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<Using Include="Xunit" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\..\src\BlazorTextDiff\BlazorTextDiff.csproj" />
25+
</ItemGroup>
26+
27+
</Project>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using DiffPlex.DiffBuilder.Model;
2+
3+
namespace BlazorTextDiff.Tests;
4+
5+
public class DiffStatsTests
6+
{
7+
[Fact]
8+
public void DefaultConstructor_AllCountsAreZero()
9+
{
10+
var stats = new DiffStats();
11+
12+
Assert.Equal(0, stats.LineAdditionCount);
13+
Assert.Equal(0, stats.LineModificationCount);
14+
Assert.Equal(0, stats.LineDeletionCount);
15+
Assert.Equal(0, stats.WordAdditionCount);
16+
Assert.Equal(0, stats.WordModificationCount);
17+
Assert.Equal(0, stats.WordDeletionCount);
18+
}
19+
20+
[Fact]
21+
public void BothPanesNull_AllCountsAreZero()
22+
{
23+
var stats = new DiffStats(null, null);
24+
25+
Assert.Equal(0, stats.LineAdditionCount);
26+
Assert.Equal(0, stats.LineModificationCount);
27+
Assert.Equal(0, stats.LineDeletionCount);
28+
Assert.Equal(0, stats.WordAdditionCount);
29+
Assert.Equal(0, stats.WordModificationCount);
30+
Assert.Equal(0, stats.WordDeletionCount);
31+
}
32+
33+
[Fact]
34+
public void EmptyPanes_AllCountsAreZero()
35+
{
36+
var oldPane = new DiffPaneModel();
37+
var newPane = new DiffPaneModel();
38+
39+
var stats = new DiffStats(oldPane, newPane);
40+
41+
Assert.Equal(0, stats.LineAdditionCount);
42+
Assert.Equal(0, stats.LineModificationCount);
43+
Assert.Equal(0, stats.LineDeletionCount);
44+
}
45+
46+
[Fact]
47+
public void CountsInsertedLinesFromNewPane()
48+
{
49+
var newPane = new DiffPaneModel();
50+
newPane.Lines.Add(new DiffPiece("line1", ChangeType.Inserted));
51+
newPane.Lines.Add(new DiffPiece("line2", ChangeType.Inserted));
52+
newPane.Lines.Add(new DiffPiece("line3", ChangeType.Unchanged));
53+
54+
var stats = new DiffStats(null, newPane);
55+
56+
Assert.Equal(2, stats.LineAdditionCount);
57+
Assert.Equal(0, stats.LineModificationCount);
58+
Assert.Equal(0, stats.LineDeletionCount);
59+
}
60+
61+
[Fact]
62+
public void CountsDeletedLinesFromOldPane()
63+
{
64+
var oldPane = new DiffPaneModel();
65+
oldPane.Lines.Add(new DiffPiece("line1", ChangeType.Deleted));
66+
oldPane.Lines.Add(new DiffPiece("line2", ChangeType.Unchanged));
67+
68+
var stats = new DiffStats(oldPane, null);
69+
70+
Assert.Equal(0, stats.LineAdditionCount);
71+
Assert.Equal(0, stats.LineModificationCount);
72+
Assert.Equal(1, stats.LineDeletionCount);
73+
}
74+
75+
[Fact]
76+
public void CountsModifiedLinesFromNewPane()
77+
{
78+
var newPane = new DiffPaneModel();
79+
newPane.Lines.Add(new DiffPiece("line1", ChangeType.Modified));
80+
newPane.Lines.Add(new DiffPiece("line2", ChangeType.Modified));
81+
newPane.Lines.Add(new DiffPiece("line3", ChangeType.Modified));
82+
83+
var stats = new DiffStats(null, newPane);
84+
85+
Assert.Equal(0, stats.LineAdditionCount);
86+
Assert.Equal(3, stats.LineModificationCount);
87+
Assert.Equal(0, stats.LineDeletionCount);
88+
}
89+
90+
[Fact]
91+
public void CountsMixedLineTypes_BothPanes()
92+
{
93+
var oldPane = new DiffPaneModel();
94+
oldPane.Lines.Add(new DiffPiece("deleted", ChangeType.Deleted));
95+
oldPane.Lines.Add(new DiffPiece("unchanged", ChangeType.Unchanged));
96+
oldPane.Lines.Add(new DiffPiece(null, ChangeType.Imaginary));
97+
98+
var newPane = new DiffPaneModel();
99+
newPane.Lines.Add(new DiffPiece("inserted", ChangeType.Inserted));
100+
newPane.Lines.Add(new DiffPiece("modified", ChangeType.Modified));
101+
newPane.Lines.Add(new DiffPiece("unchanged", ChangeType.Unchanged));
102+
103+
var stats = new DiffStats(oldPane, newPane);
104+
105+
Assert.Equal(1, stats.LineAdditionCount);
106+
Assert.Equal(1, stats.LineModificationCount);
107+
Assert.Equal(1, stats.LineDeletionCount);
108+
}
109+
110+
[Fact]
111+
public void CountsWordLevelChanges_SplitAcrossPanes()
112+
{
113+
// Word additions and modifications come from newText pane
114+
var newModifiedLine = new DiffPiece("modified line", ChangeType.Modified);
115+
newModifiedLine.SubPieces.Add(new DiffPiece("word1", ChangeType.Inserted));
116+
newModifiedLine.SubPieces.Add(new DiffPiece("word2", ChangeType.Modified));
117+
newModifiedLine.SubPieces.Add(new DiffPiece("word3", ChangeType.Unchanged));
118+
119+
var newPane = new DiffPaneModel();
120+
newPane.Lines.Add(newModifiedLine);
121+
122+
// Word deletions come from oldText pane
123+
var oldModifiedLine = new DiffPiece("modified line", ChangeType.Modified);
124+
oldModifiedLine.SubPieces.Add(new DiffPiece("word4", ChangeType.Deleted));
125+
oldModifiedLine.SubPieces.Add(new DiffPiece("word5", ChangeType.Unchanged));
126+
127+
var oldPane = new DiffPaneModel();
128+
oldPane.Lines.Add(oldModifiedLine);
129+
130+
var stats = new DiffStats(oldPane, newPane);
131+
132+
Assert.Equal(1, stats.WordAdditionCount);
133+
Assert.Equal(1, stats.WordModificationCount);
134+
Assert.Equal(1, stats.WordDeletionCount);
135+
}
136+
137+
[Fact]
138+
public void LinesWithNoSubPieces_WordCountsAreZero()
139+
{
140+
var newPane = new DiffPaneModel();
141+
newPane.Lines.Add(new DiffPiece("simple insert", ChangeType.Inserted));
142+
143+
var oldPane = new DiffPaneModel();
144+
oldPane.Lines.Add(new DiffPiece("simple delete", ChangeType.Deleted));
145+
146+
var stats = new DiffStats(oldPane, newPane);
147+
148+
Assert.Equal(0, stats.WordAdditionCount);
149+
Assert.Equal(0, stats.WordDeletionCount);
150+
Assert.Equal(0, stats.WordModificationCount);
151+
}
152+
153+
[Fact]
154+
public void MultipleModifiedLines_AggregatesWordCounts()
155+
{
156+
var line1 = new DiffPiece("line1", ChangeType.Modified);
157+
line1.SubPieces.Add(new DiffPiece("a", ChangeType.Inserted));
158+
line1.SubPieces.Add(new DiffPiece("b", ChangeType.Inserted));
159+
160+
var line2 = new DiffPiece("line2", ChangeType.Modified);
161+
line2.SubPieces.Add(new DiffPiece("c", ChangeType.Modified));
162+
163+
var newPane = new DiffPaneModel();
164+
newPane.Lines.Add(line1);
165+
newPane.Lines.Add(line2);
166+
167+
var oldLine = new DiffPiece("line1", ChangeType.Modified);
168+
oldLine.SubPieces.Add(new DiffPiece("d", ChangeType.Deleted));
169+
170+
var oldPane = new DiffPaneModel();
171+
oldPane.Lines.Add(oldLine);
172+
173+
var stats = new DiffStats(oldPane, newPane);
174+
175+
Assert.Equal(2, stats.WordAdditionCount);
176+
Assert.Equal(1, stats.WordDeletionCount);
177+
Assert.Equal(1, stats.WordModificationCount);
178+
}
179+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using Bunit;
2+
using DiffPlex.DiffBuilder.Model;
3+
4+
namespace BlazorTextDiff.Tests;
5+
6+
public class TextDiffLineTests : TestContext
7+
{
8+
[Fact]
9+
public void UnchangedLine_RendersPlainText()
10+
{
11+
var piece = new DiffPiece("Hello World", ChangeType.Unchanged);
12+
13+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
14+
.Add(p => p.Model, piece));
15+
16+
cut.MarkupMatches("Hello World");
17+
}
18+
19+
[Fact]
20+
public void InsertedLine_RendersPlainText()
21+
{
22+
var piece = new DiffPiece("New line", ChangeType.Inserted);
23+
24+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
25+
.Add(p => p.Model, piece));
26+
27+
cut.MarkupMatches("New line");
28+
}
29+
30+
[Fact]
31+
public void DeletedLine_RendersPlainText()
32+
{
33+
var piece = new DiffPiece("Old line", ChangeType.Deleted);
34+
35+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
36+
.Add(p => p.Model, piece));
37+
38+
cut.MarkupMatches("Old line");
39+
}
40+
41+
[Fact]
42+
public void ModifiedLine_RendersSubPiecesWithCssClasses()
43+
{
44+
// "same " is unchanged, "changed" is modified (a word), " " is unchanged whitespace, "added" is inserted (a word)
45+
var piece = new DiffPiece("modified", ChangeType.Modified);
46+
piece.SubPieces.Add(new DiffPiece("s", ChangeType.Unchanged));
47+
piece.SubPieces.Add(new DiffPiece("a", ChangeType.Unchanged));
48+
piece.SubPieces.Add(new DiffPiece("m", ChangeType.Unchanged));
49+
piece.SubPieces.Add(new DiffPiece("e", ChangeType.Unchanged));
50+
piece.SubPieces.Add(new DiffPiece(" ", ChangeType.Unchanged));
51+
piece.SubPieces.Add(new DiffPiece("c", ChangeType.Modified));
52+
piece.SubPieces.Add(new DiffPiece("h", ChangeType.Modified));
53+
piece.SubPieces.Add(new DiffPiece("a", ChangeType.Modified));
54+
piece.SubPieces.Add(new DiffPiece("n", ChangeType.Modified));
55+
piece.SubPieces.Add(new DiffPiece("g", ChangeType.Modified));
56+
piece.SubPieces.Add(new DiffPiece("e", ChangeType.Modified));
57+
piece.SubPieces.Add(new DiffPiece("d", ChangeType.Modified));
58+
59+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
60+
.Add(p => p.Model, piece));
61+
62+
// "same" is all unchanged → no span. "changed" is fully changed → character-level span
63+
Assert.Contains("same", cut.Markup);
64+
var spans = cut.FindAll("span");
65+
Assert.Single(spans);
66+
Assert.Contains("modified-character", spans[0].ClassList);
67+
Assert.Equal("changed", spans[0].TextContent);
68+
}
69+
70+
[Fact]
71+
public void ModifiedLine_PartiallyChangedWord_RendersWordAndCharacterSpans()
72+
{
73+
// "Program" unchanged + "m" inserted + "ing" unchanged = word "Programming" partially changed
74+
var piece = new DiffPiece("modified", ChangeType.Modified);
75+
piece.SubPieces.Add(new DiffPiece("P", ChangeType.Unchanged));
76+
piece.SubPieces.Add(new DiffPiece("r", ChangeType.Unchanged));
77+
piece.SubPieces.Add(new DiffPiece("o", ChangeType.Unchanged));
78+
piece.SubPieces.Add(new DiffPiece("g", ChangeType.Unchanged));
79+
piece.SubPieces.Add(new DiffPiece("r", ChangeType.Unchanged));
80+
piece.SubPieces.Add(new DiffPiece("a", ChangeType.Unchanged));
81+
piece.SubPieces.Add(new DiffPiece("m", ChangeType.Unchanged));
82+
piece.SubPieces.Add(new DiffPiece("m", ChangeType.Inserted));
83+
piece.SubPieces.Add(new DiffPiece("i", ChangeType.Unchanged));
84+
piece.SubPieces.Add(new DiffPiece("n", ChangeType.Unchanged));
85+
piece.SubPieces.Add(new DiffPiece("g", ChangeType.Unchanged));
86+
87+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
88+
.Add(p => p.Model, piece));
89+
90+
// Word-level span wraps the whole word, character-level span wraps the inserted "m"
91+
var wordSpan = cut.Find("span.inserted-word");
92+
Assert.NotNull(wordSpan);
93+
Assert.Equal("Programming", wordSpan.TextContent);
94+
95+
var charSpan = cut.Find("span.inserted-character");
96+
Assert.NotNull(charSpan);
97+
Assert.Equal("m", charSpan.TextContent);
98+
}
99+
100+
[Fact]
101+
public void ModifiedLine_SkipsImaginarySubPieces()
102+
{
103+
var piece = new DiffPiece("mod", ChangeType.Modified);
104+
piece.SubPieces.Add(new DiffPiece("v", ChangeType.Unchanged));
105+
piece.SubPieces.Add(new DiffPiece("i", ChangeType.Unchanged));
106+
piece.SubPieces.Add(new DiffPiece("s", ChangeType.Unchanged));
107+
piece.SubPieces.Add(new DiffPiece(null, ChangeType.Imaginary));
108+
piece.SubPieces.Add(new DiffPiece(" ", ChangeType.Unchanged));
109+
piece.SubPieces.Add(new DiffPiece("n", ChangeType.Modified));
110+
piece.SubPieces.Add(new DiffPiece("e", ChangeType.Modified));
111+
piece.SubPieces.Add(new DiffPiece("w", ChangeType.Modified));
112+
113+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
114+
.Add(p => p.Model, piece));
115+
116+
// Imaginary is skipped; "vis" is unchanged (no span), "new" is fully changed (character span)
117+
Assert.Contains("vis", cut.Markup);
118+
var changedSpan = cut.Find("span.modified-character");
119+
Assert.Equal("new", changedSpan.TextContent);
120+
121+
// Imaginary text should not appear
122+
Assert.DoesNotContain("Imaginary", cut.Markup);
123+
}
124+
125+
[Fact]
126+
public void NullOrEmptyText_RendersNothing()
127+
{
128+
var piece = new DiffPiece(null, ChangeType.Imaginary);
129+
130+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
131+
.Add(p => p.Model, piece));
132+
133+
cut.MarkupMatches(string.Empty);
134+
}
135+
136+
[Fact]
137+
public void EmptyStringText_RendersNothing()
138+
{
139+
var piece = new DiffPiece(string.Empty, ChangeType.Unchanged);
140+
141+
var cut = RenderComponent<TextDiffLine>(parameters => parameters
142+
.Add(p => p.Model, piece));
143+
144+
cut.MarkupMatches(string.Empty);
145+
}
146+
}

0 commit comments

Comments
 (0)