Skip to content

Commit 541fc19

Browse files
committed
feat: general improvements and minor bugs
1 parent 3cd3d4d commit 541fc19

3 files changed

Lines changed: 549 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace WebExpress.WebIndex.Test.Data
2+
{
3+
/// <summary>
4+
/// Represents an mock item.
5+
/// </summary>
6+
public class IndexItem : IIndexItem
7+
{
8+
/// <summary>
9+
/// Returns a new unique identifier each time the property is accessed.
10+
/// </summary>
11+
public Guid Id => Guid.NewGuid();
12+
13+
/// <summary>
14+
/// Returns or sets the name associated with the object.
15+
/// </summary>
16+
public string Name { get; set; }
17+
18+
/// <summary>
19+
/// Returns or sets the integer value associated with this instance.
20+
/// </summary>
21+
public int Value { get; set; }
22+
23+
/// <summary>
24+
/// Returns or sets a value indicating whether the object is active.
25+
/// </summary>
26+
public bool IsActive { get; set; }
27+
}
28+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
using System.Linq.Expressions;
2+
using WebExpress.WebIndex.Predicate;
3+
using WebExpress.WebIndex.Test.Data;
4+
5+
namespace WebExpress.WebIndex.Test.Predicate
6+
{
7+
/// <summary>
8+
/// Provides unit tests for the ExpressionExtensions class that compose and manipulate
9+
/// predicate expressions.
10+
/// </summary>
11+
[Collection("NonParallelTests")]
12+
public class UnitTestExpressionExtensions
13+
{
14+
/// <summary>
15+
/// Verifies that the True<T> method returns a predicate that always evaluates to true
16+
/// for any input.
17+
/// </summary>
18+
[Fact]
19+
public void True()
20+
{
21+
// arrange
22+
var items = new[]
23+
{
24+
new IndexItem { Name = "A", Value = 1, IsActive = true },
25+
new IndexItem { Name = "B", Value = 2, IsActive = false }
26+
}.AsQueryable();
27+
28+
// act
29+
var pred = ExpressionExtensions.True<IndexItem>();
30+
var compiled = pred.Compile();
31+
32+
// validation
33+
Assert.All(items, item => Assert.True(compiled(item)));
34+
Assert.Equal(2, items.Where(pred).Count());
35+
}
36+
37+
/// <summary>
38+
/// Verifies that the False method returns a predicate expression that always evaluates
39+
/// to false for any input.
40+
/// </summary>
41+
[Fact]
42+
public void False()
43+
{
44+
// arrange
45+
var items = new[]
46+
{
47+
new IndexItem { Name = "A", Value = 1, IsActive = true },
48+
new IndexItem { Name = "B", Value = 2, IsActive = false }
49+
}.AsQueryable();
50+
51+
// act
52+
var pred = ExpressionExtensions.False<IndexItem>();
53+
var compiled = pred.Compile();
54+
55+
// validation
56+
Assert.All(items, item => Assert.False(compiled(item)));
57+
Assert.Empty(items.Where(pred));
58+
}
59+
60+
/// <summary>
61+
/// Verifies that the Not extension method correctly negates a predicate expression.
62+
/// </summary>
63+
[Fact]
64+
public void Not()
65+
{
66+
// arrange
67+
var items = new[]
68+
{
69+
new IndexItem { Name = "A", Value = 1, IsActive = true },
70+
new IndexItem { Name = "B", Value = 2, IsActive = false }
71+
}.AsQueryable();
72+
73+
// act
74+
Expression<Func<IndexItem, bool>> pred = x => x.IsActive;
75+
var negated = pred.Not();
76+
var compiled = negated.Compile();
77+
78+
// validation
79+
Assert.True(compiled(items.First(i => !i.IsActive)));
80+
Assert.False(compiled(items.First(i => i.IsActive)));
81+
}
82+
83+
/// <summary>
84+
/// Verifies that the And extension method correctly composes two predicate
85+
/// expressions using a logical AND operation.
86+
/// </summary>
87+
[Fact]
88+
public void And()
89+
{
90+
// arrange
91+
var items = new[]
92+
{
93+
new IndexItem { Name = "Ax", Value = 10, IsActive = true },
94+
new IndexItem { Name = "Bx", Value = 10, IsActive = false },
95+
new IndexItem { Name = "Ay", Value = 20, IsActive = true }
96+
}.AsQueryable();
97+
98+
// act
99+
Expression<Func<IndexItem, bool>> isActive = x => x.IsActive;
100+
Expression<Func<IndexItem, bool>> valueTen = x => x.Value == 10;
101+
var andPred = isActive.And(valueTen);
102+
103+
// validation
104+
Assert.Single(items.Where(andPred));
105+
Assert.Equal("Ax", items.Where(andPred).First().Name);
106+
}
107+
108+
/// <summary>
109+
/// Verifies that the Or extension method correctly composes two predicate
110+
/// expressions using a logical OR operation.
111+
/// </summary>
112+
[Fact]
113+
public void Or()
114+
{
115+
// arrange
116+
var items = new[]
117+
{
118+
new IndexItem { Name = "Ax", Value = 10, IsActive = true },
119+
new IndexItem { Name = "Bx", Value = 20, IsActive = false },
120+
new IndexItem { Name = "Ay", Value = 30, IsActive = false }
121+
}.AsQueryable();
122+
123+
// act
124+
Expression<Func<IndexItem, bool>> value10 = x => x.Value == 10;
125+
Expression<Func<IndexItem, bool>> value20 = x => x.Value == 20;
126+
var pred = value10.Or(value20);
127+
128+
// validation
129+
var result = items.Where(pred).ToList();
130+
Assert.Equal(2, result.Count);
131+
Assert.Contains(result, x => x.Name == "Ax");
132+
Assert.Contains(result, x => x.Name == "Bx");
133+
}
134+
135+
/// <summary>
136+
/// Verifies that the All method returns a predicate representing the
137+
/// logical AND of all provided predicates, and that it returns a predicate
138+
/// that always evaluates to true when no predicates are supplied.
139+
/// </summary>
140+
[Fact]
141+
public void All()
142+
{
143+
// arrange
144+
var items = new[]
145+
{
146+
new IndexItem { Name = "Ax", Value = 10, IsActive = true },
147+
new IndexItem { Name = "Bx", Value = 10, IsActive = false },
148+
new IndexItem { Name = "Ay", Value = 20, IsActive = true }
149+
}.AsQueryable();
150+
151+
// act
152+
var pred = ExpressionExtensions.All<IndexItem>(
153+
x => x.Value == 10,
154+
x => x.IsActive);
155+
156+
// validation
157+
var result = items.Where(pred).ToList();
158+
Assert.Single(result);
159+
Assert.Equal("Ax", result[0].Name);
160+
161+
// when empty - should always return true
162+
var predTrue = ExpressionExtensions.All<IndexItem>();
163+
Assert.All(items, item => Assert.True(predTrue.Compile()(item)));
164+
}
165+
166+
/// <summary>
167+
/// Verifies that the Any method returns a predicate representing the
168+
/// logical OR of the provided predicates.
169+
/// </summary>
170+
[Fact]
171+
public void Any()
172+
{
173+
// arrange
174+
var items = new[]
175+
{
176+
new IndexItem { Name = "Ax", Value = 10, IsActive = true },
177+
new IndexItem { Name = "Bx", Value = 20, IsActive = false },
178+
new IndexItem { Name = "Ay", Value = 30, IsActive = true }
179+
}.AsQueryable();
180+
181+
// act
182+
var pred = ExpressionExtensions.Any<IndexItem>(
183+
x => x.Value == 10,
184+
x => x.Value == 20);
185+
186+
// validation
187+
var result = items.Where(pred).ToList();
188+
Assert.Equal(2, result.Count);
189+
Assert.Contains(result, x => x.Name == "Ax");
190+
Assert.Contains(result, x => x.Name == "Bx");
191+
192+
// when empty - should always return false
193+
var predFalse = ExpressionExtensions.Any<IndexItem>();
194+
Assert.All(items, item => Assert.False(predFalse.Compile()(item)));
195+
}
196+
197+
/// <summary>
198+
/// Verifies that combining two predicates using the And extension method
199+
/// produces a merged predicate that filters items matching both conditions.
200+
/// </summary>
201+
[Fact]
202+
public void Combine()
203+
{
204+
// arrange
205+
var items = new[]
206+
{
207+
new IndexItem { Name = "A", Value = 7, IsActive = true },
208+
new IndexItem { Name = "B", Value = 8, IsActive = false },
209+
new IndexItem { Name = "C", Value = 8, IsActive = true }
210+
}.AsQueryable();
211+
212+
Expression<Func<IndexItem, bool>> pred1 = x => x.Value == 8;
213+
Expression<Func<IndexItem, bool>> pred2 = x => x.IsActive;
214+
215+
// act
216+
// test internal Combine via And (public)
217+
var combined = pred1.And(pred2);
218+
219+
// validation
220+
var result = items.Where(combined).ToList();
221+
Assert.Single(result);
222+
Assert.Equal("C", result[0].Name);
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)