Skip to content

Commit c6b6b35

Browse files
committed
Add tests for DrawingManager.
1 parent 51cc2c4 commit c6b6b35

1 file changed

Lines changed: 313 additions & 0 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
namespace AzureMapsControl.Components.Tests.Drawing
2+
{
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Threading.Tasks;
7+
8+
using AzureMapsControl.Components.Atlas;
9+
using AzureMapsControl.Components.Drawing;
10+
using AzureMapsControl.Components.Exceptions;
11+
using AzureMapsControl.Components.Runtime;
12+
13+
using Microsoft.Extensions.Logging;
14+
15+
using Moq;
16+
17+
using Xunit;
18+
19+
public class DrawingManagerTests
20+
{
21+
private readonly Mock<IMapJsRuntime> _jsRuntimeMock = new();
22+
private readonly Mock<ILogger> _loggerMock = new();
23+
24+
[Fact]
25+
public void Should_HaveDefaultProperties()
26+
{
27+
var drawingManager = new DrawingManager();
28+
29+
Assert.False(drawingManager.Disposed);
30+
Assert.Null(drawingManager.JSRuntime);
31+
Assert.Null(drawingManager.Logger);
32+
}
33+
34+
[Fact]
35+
public void Should_SetJSRuntimeAndLogger()
36+
{
37+
var drawingManager = CreateInitializedDrawingManager();
38+
39+
Assert.Equal(_jsRuntimeMock.Object, drawingManager.JSRuntime);
40+
Assert.Equal(_loggerMock.Object, drawingManager.Logger);
41+
}
42+
43+
[Fact]
44+
public async Task Should_AddShapes_AllSupportedGeometryTypes_Async()
45+
{
46+
var drawingManager = CreateInitializedDrawingManager();
47+
48+
var shapes = new List<Shape>
49+
{
50+
new Shape<Point>(new Point()),
51+
new Shape<LineString>(new LineString()),
52+
new Shape<MultiLineString>(new MultiLineString()),
53+
new Shape<MultiPoint>(new MultiPoint()),
54+
new Shape<MultiPolygon>(new MultiPolygon()),
55+
new Shape<Polygon>(new Polygon()),
56+
new Shape<RoutePoint>(new RoutePoint()),
57+
};
58+
59+
await drawingManager.AddShapesAsync(shapes);
60+
61+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Point>>>(s => s.Count() == 1)), Times.Once);
62+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<LineString>>>(s => s.Count() == 1)), Times.Once);
63+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<MultiLineString>>>(s => s.Count() == 1)), Times.Once);
64+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<MultiPoint>>>(s => s.Count() == 1)), Times.Once);
65+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<MultiPolygon>>>(s => s.Count() == 1)), Times.Once);
66+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Polygon>>>(s => s.Count() == 1)), Times.Once);
67+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<RoutePoint>>>(s => s.Count() == 1)), Times.Once);
68+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.IsAny<object[]>()), Times.Exactly(7));
69+
70+
_jsRuntimeMock.VerifyNoOtherCalls();
71+
}
72+
73+
[Fact]
74+
public async Task Should_AddMultipleShapesOfSameType_Async()
75+
{
76+
var drawingManager = CreateInitializedDrawingManager();
77+
78+
var shapes = new List<Shape>
79+
{
80+
new Shape<Point>(new Point()),
81+
new Shape<Point>(new Point()),
82+
new Shape<Point>(new Point())
83+
};
84+
85+
await drawingManager.AddShapesAsync(shapes);
86+
87+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Point>>>(s => s.Count() == 3)), Times.Once);
88+
_jsRuntimeMock.VerifyNoOtherCalls();
89+
}
90+
91+
[Fact]
92+
public async Task Should_HandleMixedGeometryTypes_InSingleCall_Async()
93+
{
94+
var drawingManager = CreateInitializedDrawingManager();
95+
96+
var shapes = new List<Shape>
97+
{
98+
new Shape<Point>(new Point()),
99+
new Shape<Point>(new Point()),
100+
new Shape<LineString>(new LineString()),
101+
new Shape<Polygon>(new Polygon()),
102+
new Shape<Polygon>(new Polygon()),
103+
new Shape<Polygon>(new Polygon())
104+
};
105+
106+
await drawingManager.AddShapesAsync(shapes);
107+
108+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Point>>>(s => s.Count() == 2)), Times.Once);
109+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<LineString>>>(s => s.Count() == 1)), Times.Once);
110+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Polygon>>>(s => s.Count() == 3)), Times.Once);
111+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.IsAny<object[]>()), Times.Exactly(3));
112+
113+
_jsRuntimeMock.VerifyNoOtherCalls();
114+
}
115+
116+
[Fact]
117+
public async Task Should_HandleLargeNumberOfShapes_Efficiently_Async()
118+
{
119+
var drawingManager = CreateInitializedDrawingManager();
120+
121+
// Create a large collection of shapes
122+
var shapes = new List<Shape>();
123+
for (int i = 0; i < 1000; i++)
124+
{
125+
shapes.Add(new Shape<Point>(new Point()));
126+
}
127+
128+
await drawingManager.AddShapesAsync(shapes);
129+
130+
// Should still only make one call per geometry type
131+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.Is<IEnumerable<Shape<Point>>>(s => s.Count() == 1000)), Times.Once);
132+
_jsRuntimeMock.VerifyNoOtherCalls();
133+
}
134+
135+
[Fact]
136+
public async Task Should_NotAddShapes_WhenNull_Async()
137+
{
138+
var drawingManager = CreateInitializedDrawingManager();
139+
140+
await drawingManager.AddShapesAsync(null);
141+
142+
_jsRuntimeMock.VerifyNoOtherCalls();
143+
}
144+
145+
[Fact]
146+
public async Task Should_NotAddShapes_WhenEmpty_Async()
147+
{
148+
var drawingManager = CreateInitializedDrawingManager();
149+
150+
await drawingManager.AddShapesAsync(new List<Shape>());
151+
152+
_jsRuntimeMock.VerifyNoOtherCalls();
153+
}
154+
155+
[Fact]
156+
public async Task Should_ClearShapes_Async()
157+
{
158+
var drawingManager = CreateInitializedDrawingManager();
159+
160+
await drawingManager.ClearAsync();
161+
162+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.Clear.ToDrawingNamespace()), Times.Once);
163+
_jsRuntimeMock.VerifyNoOtherCalls();
164+
}
165+
166+
[Fact]
167+
public async Task Should_AccumulateShapes_InInternalState_Async()
168+
{
169+
var drawingManager = CreateInitializedDrawingManager();
170+
171+
var firstBatch = new List<Shape> { new Shape<Point>(new Point()) };
172+
var secondBatch = new List<Shape> { new Shape<LineString>(new LineString()) };
173+
var thirdBatch = new List<Shape> { new Shape<Point>(new Point()) };
174+
175+
await drawingManager.AddShapesAsync(firstBatch);
176+
await drawingManager.AddShapesAsync(secondBatch);
177+
await drawingManager.AddShapesAsync(thirdBatch);
178+
179+
// Verify internal state accumulates all shapes
180+
var sourceShapes = GetInternalSourceShapes(drawingManager);
181+
Assert.Equal(3, sourceShapes.Count);
182+
Assert.Contains(firstBatch[0], sourceShapes);
183+
Assert.Contains(secondBatch[0], sourceShapes);
184+
Assert.Contains(thirdBatch[0], sourceShapes);
185+
186+
// Verify correct number of JS calls
187+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(
188+
Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(),
189+
It.IsAny<object[]>()), Times.Exactly(3)); // 1 Point call + 1 LineString call + 1 more Point call
190+
}
191+
192+
[Fact]
193+
public async Task Should_ClearInternalState_WhenCleared_Async()
194+
{
195+
var drawingManager = CreateInitializedDrawingManager();
196+
var shapes = new List<Shape> { new Shape<Point>(new Point()) };
197+
198+
// Add shapes to initialize and populate internal state
199+
await drawingManager.AddShapesAsync(shapes);
200+
var sourceShapes = GetInternalSourceShapes(drawingManager);
201+
Assert.NotNull(sourceShapes);
202+
Assert.Single(sourceShapes);
203+
204+
// Clear should reset internal state
205+
await drawingManager.ClearAsync();
206+
sourceShapes = GetInternalSourceShapes(drawingManager);
207+
Assert.Null(sourceShapes);
208+
209+
// Adding again should reinitialize state
210+
await drawingManager.AddShapesAsync(shapes);
211+
sourceShapes = GetInternalSourceShapes(drawingManager);
212+
Assert.NotNull(sourceShapes);
213+
Assert.Single(sourceShapes);
214+
215+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.AddShapes.ToDrawingNamespace(), It.IsAny<object[]>()), Times.Exactly(2));
216+
_jsRuntimeMock.Verify(runtime => runtime.InvokeVoidAsync(Constants.JsConstants.Methods.Source.Clear.ToDrawingNamespace()), Times.Once);
217+
_jsRuntimeMock.VerifyNoOtherCalls();
218+
}
219+
220+
[Fact]
221+
public void Should_Dispose_Successfully()
222+
{
223+
var drawingManager = CreateInitializedDrawingManager();
224+
225+
Assert.False(drawingManager.Disposed);
226+
227+
drawingManager.Dispose();
228+
229+
Assert.True(drawingManager.Disposed);
230+
}
231+
232+
[Fact]
233+
public async Task Should_ThrowComponentNotAddedToMapException_WhenJSRuntimeIsNull_Clear_Async()
234+
{
235+
var drawingManager = new DrawingManager();
236+
237+
await Assert.ThrowsAsync<ComponentNotAddedToMapException>(async () => await drawingManager.ClearAsync());
238+
239+
_jsRuntimeMock.VerifyNoOtherCalls();
240+
}
241+
242+
[Fact]
243+
public async Task Should_ThrowComponentNotAddedToMapException_WhenJSRuntimeIsNull_AddShapes_Async()
244+
{
245+
var drawingManager = new DrawingManager();
246+
var shapes = new List<Shape> { new Shape<Point>(new Point()) };
247+
248+
await Assert.ThrowsAsync<ComponentNotAddedToMapException>(async () => await drawingManager.AddShapesAsync(shapes));
249+
250+
_jsRuntimeMock.VerifyNoOtherCalls();
251+
}
252+
253+
[Fact]
254+
public void Should_ThrowComponentNotAddedToMapException_WhenJSRuntimeIsNull_Dispose()
255+
{
256+
var drawingManager = new DrawingManager();
257+
258+
Assert.Throws<ComponentNotAddedToMapException>(() => drawingManager.Dispose());
259+
}
260+
261+
[Fact]
262+
public async Task Should_ThrowComponentDisposedException_WhenDisposed_Clear_Async()
263+
{
264+
var drawingManager = CreateInitializedDrawingManager();
265+
266+
drawingManager.Dispose();
267+
268+
await Assert.ThrowsAsync<ComponentDisposedException>(async () => await drawingManager.ClearAsync());
269+
270+
_jsRuntimeMock.VerifyNoOtherCalls();
271+
}
272+
273+
[Fact]
274+
public async Task Should_ThrowComponentDisposedException_WhenDisposed_AddShapes_Async()
275+
{
276+
var drawingManager = CreateInitializedDrawingManager();
277+
278+
drawingManager.Dispose();
279+
var shapes = new List<Shape> { new Shape<Point>(new Point()) };
280+
281+
await Assert.ThrowsAsync<ComponentDisposedException>(async () => await drawingManager.AddShapesAsync(shapes));
282+
283+
_jsRuntimeMock.VerifyNoOtherCalls();
284+
}
285+
286+
[Fact]
287+
public void Should_ThrowComponentDisposedException_WhenAlreadyDisposed_Dispose()
288+
{
289+
var drawingManager = CreateInitializedDrawingManager();
290+
drawingManager.Dispose();
291+
292+
Assert.Throws<ComponentDisposedException>(() => drawingManager.Dispose());
293+
}
294+
295+
private DrawingManager CreateInitializedDrawingManager()
296+
{
297+
return new DrawingManager() {
298+
JSRuntime = _jsRuntimeMock.Object,
299+
Logger = _loggerMock.Object
300+
};
301+
}
302+
303+
/// <summary>
304+
/// Uses reflection to access the private _sourceShapes field for testing internal state
305+
/// </summary>
306+
private List<Shape> GetInternalSourceShapes(DrawingManager drawingManager)
307+
{
308+
var field = typeof(DrawingManager).GetField("_sourceShapes",
309+
BindingFlags.NonPublic | BindingFlags.Instance);
310+
return field?.GetValue(drawingManager) as List<Shape>;
311+
}
312+
}
313+
}

0 commit comments

Comments
 (0)