summaryrefslogtreecommitdiff
path: root/Plugins/MonoGame.Extended/tests
diff options
context:
space:
mode:
Diffstat (limited to 'Plugins/MonoGame.Extended/tests')
-rw-r--r--Plugins/MonoGame.Extended/tests/Directory.Build.props46
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/CollisionComponentTests.cs416
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicActor.cs33
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicWall.cs20
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj7
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/QuadTreeTests.cs446
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs39
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/packages.config6
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj24
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric.tmx76
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric_tileset.pngbin0 -> 4599 bytes
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/level01.tmx440
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/template.tx4
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-object-layer.tmx24
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-base64.tmx11
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-csv.tmx13
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-gzip.tmx11
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-xml.tmx19
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-zlib.tmx11
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TiledMapImporterProcessorTests.cs205
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorImporterTests.cs34
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorProcessorTests.cs30
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj16
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator-atlas.json390
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator.aa15
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/test-tileset.json93
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TexturePackerJsonImporterProcessorTests.cs33
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectBuilderTests.cs70
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectTests.cs88
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/BitArrayExtensionsTests.cs20
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs47
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentMapperTests.cs95
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentTypeTests.cs20
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj7
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/WorldManagerTests.cs71
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs122
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiControlCollectionTests.cs58
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/GuiRendererTests.cs34
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj7
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AngleTest.cs92
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AssertExtensions.cs21
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/BitmapFonts/BitmapFontTests.cs79
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Camera2DTests.cs97
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/CollectionAssert.cs14
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/BagTests.cs48
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/DequeTests.cs408
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Content/ContentReaderExtensionsTests.cs23
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MockGameWindow.cs42
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj12
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/OpenTK.dll.config26
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs25
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ColourTests.cs126
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs131
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ParticleBufferTests.cs184
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/PointProfileTests.cs33
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/RingProfileTests.cs38
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/BoundingRectangleTests.cs396
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/CircleFTests.cs420
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/EllipseFTest.cs38
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs234
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Point2Tests.cs356
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Ray2DTests.cs217
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/RectangleFTests.cs135
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Segment2DTests.cs251
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs180
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Size2Tests.cs304
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/RangeTests.cs102
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs66
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs36
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Shapes/PolygonFTests.cs87
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteSheetAnimationTests.cs910
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteTests.cs91
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGame.cs21
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGraphicsDevice.cs12
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestHelper.cs27
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureAtlasTests.cs236
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureRegion2DTests.cs40
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Vector2ExtensionsTests.cs101
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/BoxingViewportAdapterTests.cs41
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/DefaultViewportAdapterTests.cs26
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/WithinDeltaEqualityComparer.cs24
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/FullMapRendererTest.cs291
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj7
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/TiledTilesetTests.cs130
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/ColorHandler.cs8
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj7
-rw-r--r--Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/TweenerTests.cs15
87 files changed, 9309 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/tests/Directory.Build.props b/Plugins/MonoGame.Extended/tests/Directory.Build.props
new file mode 100644
index 0000000..06b0a41
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/Directory.Build.props
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project>
+
+ <Import Project="$(MSBuildThisFileDirectory)..\Directory.Build.props" />
+
+ <PropertyGroup>
+ <ArtifactsPath>$(SolutionDirectory).artifacts/tests</ArtifactsPath>
+ <ProjectCategory>tests</ProjectCategory>
+ <GenerateDocumentationFile>false</GenerateDocumentationFile>
+ <IsPackable>false</IsPackable>
+ <NoWarn>CA1707</NoWarn>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="MonoGame.Framework.DesktopGL"
+ Version="3.8.1.303" />
+
+ <PackageReference Include="NSubstitute"
+ Version="4.2.2" />
+
+ <PackageReference Include="xunit"
+ Version="2.6.2"
+ IsImplicitlyDefined="true" />
+
+ <PackageReference Include="Microsoft.NET.Test.Sdk"
+ Version="17.6.0"
+ IsImplicitlyDefined="true" />
+
+ <PackageReference Include="xunit.runner.visualstudio"
+ Version="2.5.6"
+ IsImplicitlyDefined="true"
+ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive"
+ PrivateAssets="all" />
+
+ <PackageReference Include="coverlet.collector"
+ Version="6.0.0"
+ IsImplicitlyDefined="true"
+ IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive"
+ PrivateAssets="all" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Using Include="Xunit" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/CollisionComponentTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/CollisionComponentTests.cs
new file mode 100644
index 0000000..39ac707
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/CollisionComponentTests.cs
@@ -0,0 +1,416 @@
+using System;
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Collisions.Tests
+{
+ using MonoGame.Extended.Collisions.Layers;
+
+ /// <summary>
+ /// Test collision of actors with various shapes.
+ /// </summary>
+ /// <remarks>
+ /// Uses the fact that <see cref="BasicActor"/> moves itself away from
+ /// <see cref="BasicWall"/> on collision.
+ /// </remarks>
+ public class CollisionComponentTests
+ {
+ private readonly CollisionComponent _collisionComponent;
+ private readonly GameTime _gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromMilliseconds(16));
+
+ public CollisionComponentTests()
+ {
+ _collisionComponent = new CollisionComponent(new RectangleF(Point2.Zero, new Point2(10, 10)));
+ }
+
+ #region Circle Circle
+
+ [Fact]
+ public void PenetrationVectorSameCircleTest()
+ {
+ Point2 pos1 = Point2.Zero;
+ Point2 pos2 = Point2.Zero;
+
+ IShapeF shape1 = new CircleF(pos1, 2.0f);
+ IShapeF shape2 = new CircleF(pos2, 2.0f);
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.Y - -4f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationVectorSlightlyOverlappingCircleTest()
+ {
+ Point2 pos1 = new Point2(0, 1.5f);
+ Point2 pos2 = Point2.Zero;
+
+ IShapeF shape1 = new CircleF(pos1, 2.0f);
+ IShapeF shape2 = new CircleF(pos2, 2.0f);
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ // Actor should have moved up because the distance is shorter.
+ Assert.True(actor1.Position.Y > actor2.Position.Y);
+ // The circle centers should be about 4 units away after moving
+ Assert.True(Math.Abs(actor1.Position.Y - 4.0f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationVectorSlightlyOverlappingOffAxisTest()
+ {
+ Point2 pos1 = new Point2(2, 2.5f);
+ Point2 pos2 = new Point2(2, 1);
+
+ IShapeF shape1 = new CircleF(pos1, 2.0f);
+ IShapeF shape2 = new CircleF(pos2, 2.0f);
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ // Actor should have moved up because the distance is shorter.
+ Assert.True(actor1.Position.Y > actor2.Position.Y);
+ // The circle centers should be about 4 units away after moving
+ Assert.True(Math.Abs(actor1.Position.Y - 5.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationZeroRadiusCircleCircleTest()
+ {
+ Point2 pos1 = new Point2(0, 1.5f);
+ Point2 pos2 = Point2.Zero;
+
+ IShapeF shape1 = new CircleF(pos1, 0);
+ IShapeF shape2 = new CircleF(pos2, 2.0f);
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ // Actor should have moved up because the distance is shorter.
+ Assert.True(actor1.Position.Y > actor2.Position.Y);
+ // The circle centers should be about 4 units away after moving
+ // Assert.True(Math.Abs(actor1.Position.Y - 2.0f) < float.Epsilon);
+ }
+
+ #endregion
+
+ #region Circle Rectangle
+
+ [Fact]
+ public void PenetrationVectorCircleRectangleTest()
+ {
+ Point2 pos1 = new Point2(0, 1);
+ Point2 pos2 = new Point2(-2, -1);
+
+ IShapeF shape1 = new CircleF(pos1, 2.0f);
+ IShapeF shape2 = new RectangleF(pos2, new Size2(4, 2));
+
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationVectorCircleContainedInRectangleTest()
+ {
+ Point2 pos1 = new Point2(0, 0);
+ Point2 pos2 = new Point2(-2, -1);
+
+ IShapeF shape1 = new CircleF(pos1, 1.0f);
+ IShapeF shape2 = new RectangleF(pos2, new Size2(4, 2));
+
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.Y - -2.0f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationVectorCircleOffAxisRectangleTest()
+ {
+ Point2 pos1 = new Point2(2, 1);
+ Point2 pos2 = new Point2(-2, -1);
+
+ IShapeF shape1 = new CircleF(pos1, 2.0f);
+ IShapeF shape2 = new RectangleF(pos2, new Size2(4, 2));
+
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.X - 2.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
+ }
+
+ #endregion
+
+ #region Rectangle Rectangle
+
+ [Fact]
+ public void PenetrationVectorRectangleRectangleTest()
+ {
+ Point2 pos1 = new Point2(0, 0);
+ Point2 pos2 = new Point2(-2, -1);
+
+ IShapeF shape1 = new RectangleF(pos1, new Size2(4, 2));
+ IShapeF shape2 = new RectangleF(pos2, new Size2(4, 2));
+
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.X - 0.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.Y - 1.0f) < float.Epsilon);
+ }
+
+ [Fact]
+ public void PenetrationVectorRectangleRectangleOffAxisTest()
+ {
+ Point2 pos1 = new Point2(4, 2);
+ Point2 pos2 = new Point2(3, 1);
+
+ IShapeF shape1 = new RectangleF(pos1, new Size2(4, 2));
+ IShapeF shape2 = new RectangleF(pos2, new Size2(4, 2));
+
+
+ var actor1 = new BasicActor()
+ {
+ Position = pos1,
+ Bounds = shape1
+ };
+ var actor2 = new BasicWall()
+ {
+ Position = pos2,
+ Bounds = shape2
+ };
+
+ Assert.True(shape1.Intersects(shape2));
+ _collisionComponent.Insert(actor1);
+ _collisionComponent.Insert(actor2);
+ _collisionComponent.Update(_gameTime);
+ Assert.True(Math.Abs(actor1.Position.X - 4.0f) < float.Epsilon);
+ Assert.True(Math.Abs(actor1.Position.Y - 3.0f) < float.Epsilon);
+ }
+
+ #endregion
+
+ public class Behaviours : CollisionComponentTests
+ {
+ [Fact]
+ public void Actors_is_colliding()
+ {
+ var staticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var anotherStaticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var staticActor = new CollisionIndicatingActor(staticBounds);
+ var anotherStaticActor = new CollisionIndicatingActor(anotherStaticBounds);
+ _collisionComponent.Insert(staticActor);
+ _collisionComponent.Insert(anotherStaticActor);
+
+ _collisionComponent.Update(_gameTime);
+
+ Assert.True(staticActor.IsColliding);
+ Assert.True(anotherStaticActor.IsColliding);
+ }
+
+ [Fact]
+ public void Actors_is_not_colliding_when_dynamic_actor_is_moved_out_of_collision_bounds()
+ {
+ var staticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var dynamicBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var staticActor = new CollisionIndicatingActor(staticBounds);
+ var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
+ _collisionComponent.Insert(staticActor);
+ _collisionComponent.Insert(dynamicActor);
+ dynamicActor.MoveTo(new Point2(2, 2));
+
+ _collisionComponent.Update(_gameTime);
+
+ Assert.False(staticActor.IsColliding);
+ Assert.False(dynamicActor.IsColliding);
+ }
+
+ [Fact]
+ public void Actors_is_colliding_when_dynamic_actor_is_moved_after_update()
+ {
+ var staticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var staticActor = new CollisionIndicatingActor(staticBounds);
+ _collisionComponent.Insert(staticActor);
+ for (int i = 0; i < QuadTree.QuadTree.DefaultMaxObjectsPerNode; i++)
+ {
+ var fillerBounds = new RectangleF(new Point2(0, 2), new Size2(.1f, .1f));
+ var fillerActor = new CollisionIndicatingActor(fillerBounds);
+ _collisionComponent.Insert(fillerActor);
+ }
+
+ var dynamicBounds = new RectangleF(new Point2(2, 2), new Size2(1, 1));
+ var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
+ _collisionComponent.Insert(dynamicActor);
+
+ _collisionComponent.Update(_gameTime);
+ Assert.False(staticActor.IsColliding);
+ Assert.False(dynamicActor.IsColliding);
+
+ dynamicActor.MoveTo(new Point2(0, 0));
+
+ _collisionComponent.Update(_gameTime);
+ Assert.True(dynamicActor.IsColliding);
+ Assert.True(staticActor.IsColliding);
+ }
+
+ [Fact]
+ public void Actors_is_colliding_when_dynamic_actor_is_moved_into_collision_bounds()
+ {
+ var staticBounds = new RectangleF(new Point2(0, 0), new Size2(1, 1));
+ var dynamicBounds = new RectangleF(new Point2(2, 2), new Size2(1, 1));
+ var staticActor = new CollisionIndicatingActor(staticBounds);
+ var dynamicActor = new CollisionIndicatingActor(dynamicBounds);
+ _collisionComponent.Insert(staticActor);
+ _collisionComponent.Insert(dynamicActor);
+ dynamicActor.MoveTo(new Point2(0, 0));
+
+ _collisionComponent.Update(_gameTime);
+
+ Assert.True(staticActor.IsColliding);
+ Assert.True(dynamicActor.IsColliding);
+ }
+
+ [Fact]
+ public void InsertActor_ThrowsUndefinedLayerException_IfThereIsNoLayerDefined()
+ {
+ var sut = new CollisionComponent();
+
+ var act = () => sut.Insert(new CollisionIndicatingActor(RectangleF.Empty));
+
+ Assert.Throws<UndefinedLayerException>(act);
+ }
+
+ private class CollisionIndicatingActor : ICollisionActor
+ {
+ private RectangleF _bounds;
+
+ public CollisionIndicatingActor(RectangleF bounds)
+ {
+ _bounds = bounds;
+ }
+
+ public IShapeF Bounds => _bounds;
+
+ public void OnCollision(CollisionEventArgs collisionInfo)
+ {
+ IsColliding = true;
+ }
+
+ public bool IsColliding { get; private set; }
+
+ public void MoveTo(Point2 position)
+ {
+ _bounds = new RectangleF(position, _bounds.Size);
+ }
+ }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicActor.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicActor.cs
new file mode 100644
index 0000000..fe56fdc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicActor.cs
@@ -0,0 +1,33 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Collisions.Tests
+{
+ public class BasicActor : ICollisionActor
+ {
+ public Vector2 Position { get; set; }
+ public IShapeF Bounds { get; set; }
+ public Vector2 Velocity { get; set; }
+
+ public BasicActor()
+ {
+ Bounds = new RectangleF(0f, 0f, 1f, 1f);
+ }
+ public void OnCollision(CollisionEventArgs collisionInfo)
+ {
+ Bounds.Position -= collisionInfo.PenetrationVector;
+ Position -= collisionInfo.PenetrationVector;
+
+ if (collisionInfo.Other is BasicActor)
+ {
+ CollisionCount++;
+ }
+ else
+ {
+ Console.WriteLine(collisionInfo.Other.GetType().Name);
+ }
+ }
+
+ public int CollisionCount { get; set; }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicWall.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicWall.cs
new file mode 100644
index 0000000..3492735
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/Implementation/BasicWall.cs
@@ -0,0 +1,20 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Collisions.Tests
+{
+ public class BasicWall : ICollisionActor
+ {
+ public Vector2 Position { get; set; }
+ public IShapeF Bounds { get; set; }
+ public Vector2 Velocity { get; set; }
+
+ public BasicWall()
+ {
+ Bounds = new RectangleF(0f, 0f, 1f, 1f);
+ }
+ public void OnCollision(CollisionEventArgs collisionInfo)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj
new file mode 100644
index 0000000..c62c1cd
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Collisions\MonoGame.Extended.Collisions.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/QuadTreeTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/QuadTreeTests.cs
new file mode 100644
index 0000000..1013288
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/QuadTreeTests.cs
@@ -0,0 +1,446 @@
+using System.Collections.Generic;
+using MonoGame.Extended.Collisions.QuadTree;
+using Xunit;
+
+namespace MonoGame.Extended.Collisions.Tests
+{
+ public class QuadTreeTests
+ {
+ private QuadTree.QuadTree MakeTree()
+ {
+ // Bounds set to ensure actors will fit inside the tree with default bounds.
+ var bounds = _quadTreeArea;
+ var tree = new QuadTree.QuadTree(bounds);
+
+ return tree;
+ }
+
+ private RectangleF _quadTreeArea = new RectangleF(-10f, -15, 20.0f, 30.0f);
+
+ [Fact]
+ public void ConstructorTest()
+ {
+ var bounds = new RectangleF(-10f, -15, 20.0f, 30.0f);
+ var tree = new QuadTree.QuadTree(bounds);
+
+ Assert.Equal(bounds, tree.NodeBounds);
+ Assert.True(tree.IsLeaf);
+ }
+
+ [Fact]
+ public void NumTargetsEmptyTest()
+ {
+ var tree = MakeTree();
+
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void NumTargetsOneTest()
+ {
+ var tree = MakeTree();
+ var actor = new BasicActor();
+
+ tree.Insert(new QuadtreeData(actor));
+
+ Assert.Equal(1, tree.NumTargets());
+ }
+
+
+ [Fact]
+ public void NumTargetsMultipleTest()
+ {
+ var tree = MakeTree();
+ for (int i = 0; i < 5; i++)
+ {
+ tree.Insert(new QuadtreeData(new BasicActor()));
+ }
+
+ Assert.Equal(5, tree.NumTargets());
+ }
+
+ [Fact]
+ public void NumTargetsManyTest()
+ {
+ var tree = MakeTree();
+ for (int i = 0; i < 1000; i++)
+ {
+ tree.Insert(new QuadtreeData(new BasicActor()));
+ Assert.Equal(i + 1, tree.NumTargets());
+ }
+
+ Assert.Equal(1000, tree.NumTargets());
+ }
+
+ [Fact]
+ public void InsertOneTest()
+ {
+ var tree = MakeTree();
+ var actor = new BasicActor();
+
+ tree.Insert(new QuadtreeData(actor));
+
+ Assert.Equal(1, tree.NumTargets());
+ }
+
+ [Fact]
+ public void InsertOneOverlappingQuadrantsTest()
+ {
+ var tree = MakeTree();
+ var actor = new BasicActor
+ {
+ Bounds = new RectangleF(-2.5f, -2.5f, 5f, 5f)
+ };
+
+ tree.Insert(new QuadtreeData(actor));
+
+ Assert.Equal(1, tree.NumTargets());
+ }
+
+ [Fact]
+ public void InsertMultipleTest()
+ {
+ var tree = MakeTree();
+
+ for (int i = 0; i < 10; i++)
+ {
+ tree.Insert(new QuadtreeData(new BasicActor()
+ {
+ Bounds = new RectangleF(0, 0, 1, 1)
+ }));
+ }
+
+ Assert.Equal(10, tree.NumTargets());
+ }
+
+ [Fact]
+ public void InsertManyTest()
+ {
+ var tree = MakeTree();
+
+ for (int i = 0; i < 1000; i++)
+ {
+ tree.Insert(new QuadtreeData(new BasicActor()
+ {
+ Bounds = new RectangleF(0, 0, 1, 1)
+ }));
+ }
+
+ Assert.Equal(1000, tree.NumTargets());
+ }
+
+ [Fact]
+ public void InsertMultipleOverlappingQuadrantsTest()
+ {
+ var tree = MakeTree();
+
+ for (int i = 0; i < 10; i++)
+ {
+ var actor = new BasicActor()
+ {
+ Bounds = new RectangleF(-10f, -15, 20.0f, 30.0f)
+ };
+ tree.Insert(new QuadtreeData(actor));
+ }
+
+ Assert.Equal(10, tree.NumTargets());
+ }
+
+ [Fact]
+ public void RemoveToEmptyTest()
+ {
+ var actor = new BasicActor()
+ {
+ Bounds = new RectangleF(-5f, -7f, 10.0f, 15.0f)
+ };
+ var data = new QuadtreeData(actor);
+
+ var tree = MakeTree();
+ tree.Insert(data);
+
+ tree.Remove(data);
+
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void RemoveTwoTest()
+ {
+ var tree = MakeTree();
+ var inserted = new List<QuadtreeData>();
+ var numTargets = 2;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor()
+ {
+ Bounds = new RectangleF(0, 0, 1, 1)
+ });
+ tree.Insert(data);
+ inserted.Add(data);
+ }
+
+
+ var inTree = numTargets;
+ Assert.Equal(inTree, tree.NumTargets());
+
+ foreach (var data in inserted)
+ {
+ tree.Remove(data);
+ Assert.Equal(--inTree, tree.NumTargets());
+ }
+ }
+
+ [Fact]
+ public void RemoveThreeTest()
+ {
+ var tree = MakeTree();
+ var inserted = new List<QuadtreeData>();
+ var numTargets = 3;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor()
+ {
+ Bounds = new RectangleF(0, 0, 1, 1)
+ });
+ tree.Insert(data);
+ inserted.Add(data);
+ }
+
+
+ var inTree = numTargets;
+ Assert.Equal(inTree, tree.NumTargets());
+
+ foreach (var data in inserted)
+ {
+ tree.Remove(data);
+ Assert.Equal(--inTree, tree.NumTargets());
+ }
+ }
+
+ [Fact]
+ public void RemoveManyTest()
+ {
+ var tree = MakeTree();
+ var inserted = new List<QuadtreeData>();
+ var numTargets = 1000;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor()
+ {
+ Bounds = new RectangleF(0, 0, 1, 1)
+ });
+ tree.Insert(data);
+ inserted.Add(data);
+ }
+
+
+ var inTree = numTargets;
+ Assert.Equal(inTree, tree.NumTargets());
+
+ foreach (var data in inserted)
+ {
+ data.RemoveFromAllParents();
+ Assert.Equal(--inTree, tree.NumTargets());
+ }
+ }
+
+
+ [Fact]
+ public void ShakeWhenEmptyTest()
+ {
+ var tree = MakeTree();
+ tree.Shake();
+
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeAfterSplittingWhenEmptyTest()
+ {
+ var tree = MakeTree();
+
+ tree.Split();
+ tree.Shake();
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeAfterSplittingNotEmptyTest()
+ {
+ var tree = MakeTree();
+
+ tree.Split();
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ tree.Shake();
+ Assert.Equal(1, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeWhenContainingOneTest()
+ {
+ var tree = MakeTree();
+ var numTargets = 1;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+ tree.Shake();
+ Assert.Equal(numTargets, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeWhenContainingTwoTest()
+ {
+ var tree = MakeTree();
+ var numTargets = 2;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+ tree.Shake();
+ Assert.Equal(numTargets, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeWhenContainingThreeTest()
+ {
+ var tree = MakeTree();
+ var numTargets = 3;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+ tree.Shake();
+ Assert.Equal(numTargets, tree.NumTargets());
+ }
+
+ [Fact]
+ public void ShakeWhenContainingManyTest()
+ {
+ var tree = MakeTree();
+ var numTargets = QuadTree.QuadTree.DefaultMaxObjectsPerNode + 1;
+
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+ tree.Shake();
+ Assert.Equal(numTargets, tree.NumTargets());
+ }
+
+ [Fact]
+ public void QueryWhenEmptyTest()
+ {
+ var tree = MakeTree();
+
+ var query = tree.Query(ref _quadTreeArea);
+
+ Assert.Empty(query);
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void QueryNotOverlappingTest()
+ {
+ var tree = MakeTree();
+
+ var area = new RectangleF(100f, 100f, 1f, 1f);
+ var query = tree.Query(ref area);
+
+ Assert.Empty(query);
+ Assert.Equal(0, tree.NumTargets());
+ }
+
+ [Fact]
+ public void QueryLeafNodeNotEmptyTest()
+ {
+ var tree = MakeTree();
+ var actor = new BasicActor();
+ tree.Insert(new QuadtreeData(actor));
+
+ var query = tree.Query(ref _quadTreeArea);
+ Assert.Single(query);
+ Assert.Equal(tree.NumTargets(), query.Count);
+ }
+
+ [Fact]
+ public void QueryLeafNodeNoOverlapTest()
+ {
+ var tree = MakeTree();
+ var actor = new BasicActor();
+ tree.Insert(new QuadtreeData(actor));
+
+ var area = new RectangleF(100f, 100f, 1f, 1f);
+ var query = tree.Query(ref area);
+ Assert.Empty(query);
+ }
+
+ [Fact]
+ public void QueryLeafNodeMultipleTest()
+ {
+ var tree = MakeTree();
+ var numTargets = QuadTree.QuadTree.DefaultMaxObjectsPerNode;
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+
+ var query = tree.Query(ref _quadTreeArea);
+ Assert.Equal(numTargets, query.Count);
+ Assert.Equal(tree.NumTargets(), query.Count);
+ }
+
+ [Fact]
+ public void QueryNonLeafManyTest()
+ {
+ var tree = MakeTree();
+ var numTargets = 2*QuadTree.QuadTree.DefaultMaxObjectsPerNode;
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+
+ var query = tree.Query(ref _quadTreeArea);
+ Assert.Equal(numTargets, query.Count);
+ Assert.Equal(tree.NumTargets(), query.Count);
+ }
+
+ [Fact]
+ public void QueryTwiceConsecutiveTest()
+ {
+ var tree = MakeTree();
+ var numTargets = 2 * QuadTree.QuadTree.DefaultMaxObjectsPerNode;
+ for (int i = 0; i < numTargets; i++)
+ {
+ var data = new QuadtreeData(new BasicActor());
+ tree.Insert(data);
+ }
+
+
+ var query1 = tree.Query(ref _quadTreeArea);
+ var query2 = tree.Query(ref _quadTreeArea);
+ Assert.Equal(numTargets, query1.Count);
+ Assert.Equal(tree.NumTargets(), query1.Count);
+ Assert.Equal(query1.Count, query2.Count);
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs
new file mode 100644
index 0000000..7c09a9e
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/SpatialHashTests.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace MonoGame.Extended.Collisions.Tests;
+
+public class SpatialHashTests
+{
+ private SpatialHash generateSpatialHash() => new SpatialHash(new Size2(64, 64));
+ private readonly RectangleF RECT = new RectangleF(10, 10, 20, 20);
+
+ [Fact]
+ public void CollisionOneTrueTest()
+ {
+ var hash = generateSpatialHash();
+ hash.Insert(new BasicActor()
+ {
+ Bounds = RECT,
+ });
+ var collisions = hash.Query(RECT);
+ Assert.Equal(1, collisions.Count());
+ }
+
+ [Fact]
+ public void CollisionTwoTest()
+ {
+ var hash = generateSpatialHash();
+ hash.Insert(new BasicActor
+ {
+ Bounds = RECT,
+ });
+ hash.Insert(new BasicActor
+ {
+ Bounds = RECT,
+ });
+ var collisions = hash.Query(RECT);
+ Assert.Equal(2, collisions.Count());
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/packages.config b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/packages.config
new file mode 100644
index 0000000..fa95cfc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Collisions.Tests/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="MonoGame.Framework.WindowsDX" version="3.6.0.1625" targetFramework="net452" />
+ <package id="NSubstitute" version="1.10.0.0" targetFramework="net452" />
+ <package id="NUnit" version="2.6.4" targetFramework="net452" />
+</packages> \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj
new file mode 100644
index 0000000..9c8f57c
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <PackageReference Include="MonoGame.Framework.Content.Pipeline" Version="3.8.1.303" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Content.Pipeline\MonoGame.Extended.Content.Pipeline.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Content Include="TestData\isometric.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\isometric_tileset.png" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\level01.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\template.tx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-object-layer.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-tileset-base64.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-tileset-csv.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-tileset-gzip.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-tileset-xml.tmx" CopyToOutputDirectory="PreserveNewest" />
+ <Content Include="TestData\test-tileset-zlib.tmx" CopyToOutputDirectory="PreserveNewest" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric.tmx
new file mode 100644
index 0000000..3f3168c
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric.tmx
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE map SYSTEM "http://mapeditor.org/dtd/1.0/map.dtd">
+<map version="1.0" orientation="isometric" width="40" height="40" tilewidth="64" tileheight="32">
+ <tileset firstgid="1" name="Isometric Tileset" tilewidth="64" tileheight="128">
+ <image source="isometric_tileset.png" width="128" height="128"/>
+ <tile id="0">
+ <properties>
+ <property name="obstacle" value="1"/>
+ </properties>
+ </tile>
+ </tileset>
+ <layer name="Ground" width="40" height="40">
+ <data encoding="csv">
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,1,2,1,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,1,2,1,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,1,1,1,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,1,1,2,1,1,2,1,0,0,0,0,0,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,1,2,2,2,2,1,1,0,0,2,0,0,0,0,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,1,2,2,2,2,2,2,1,0,0,0,0,0,2,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,1,1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,1,1,1,2,2,2,2,2,2,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,0,0,1,1,1,2,2,2,0,0,0,0,0,2,0,0,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,1,0,0,2,1,2,2,2,2,2,0,0,0,0,0,0,0,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,1,0,2,2,1,1,0,2,2,0,0,0,0,0,0,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,1,1,2,2,2,1,1,0,2,0,0,0,0,0,0,0,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,1,2,2,2,2,2,1,2,0,0,0,0,0,0,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,2,2,2,1,2,1,2,2,2,1,1,2,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+1,1,1,2,2,1,1,1,2,2,2,1,1,2,2,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,1,1,0,1,0,1,2,2,2,1,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,1,1,1,1,0,1,1,2,2,1,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,1,1,1,2,1,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+0,0,0,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,0,0,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,0,1,1,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,1,1,1,1,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
+</data>
+ </layer>
+ <objectgroup color="#ffaa00" name="Objects" width="40" height="40">
+ <object x="1261" y="428" width="141" height="74"/>
+ <object type="npc" x="498" y="35" width="156" height="42"/>
+ <object x="576" y="224" width="448" height="128"/>
+ <object x="2183" y="747" width="228" height="96"/>
+ <object name="fddsadsa" type="warp" x="960" y="416" width="192" height="96"/>
+ <object x="1100" y="556" width="138" height="23"/>
+ <object x="218" y="217">
+ <polyline points="0,0 14,-90 36,3"/>
+ </object>
+ <object x="243" y="174">
+ <polyline points="0,0 -17,0"/>
+ </object>
+ <object x="302" y="219">
+ <polyline points="0,0 -2,-91 -36,-94 28,-91"/>
+ </object>
+ <object x="357" y="125">
+ <polyline points="0,0 7,96 41,98"/>
+ </object>
+ </objectgroup>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric_tileset.png b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric_tileset.png
new file mode 100644
index 0000000..4c6f1b4
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/isometric_tileset.png
Binary files differ
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/level01.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/level01.tmx
new file mode 100644
index 0000000..39eb900
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/level01.tmx
@@ -0,0 +1,440 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="20" height="10" tilewidth="128" tileheight="128" backgroundcolor="#7d7d7d" nextobjectid="1">
+ <properties>
+ <property name="awesome" value="42"/>
+ </properties>
+ <tileset firstgid="1" name="free-tileset" tilewidth="128" tileheight="128" tilecount="30" spacing="2" margin="2">
+ <image source="free-tileset.png" width="652" height="783"/>
+ <tile id="7">
+ <properties>
+ <property name="frog" value="dog"/>
+ </properties>
+ </tile>
+ <tile id="23">
+ <properties>
+ <property name="element" value="box"/>
+ </properties>
+ </tile>
+ <tile id="24">
+ <properties>
+ <property name="hp" value="55"/>
+ <property name="mp" value="16"/>
+ </properties>
+ </tile>
+ </tileset>
+ <layer name="Tile Layer 2" width="20" height="10">
+ <data>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="26"/>
+ <tile gid="21"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="25"/>
+ <tile gid="29"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="21"/>
+ <tile gid="20"/>
+ <tile gid="27"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="20"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="21"/>
+ <tile gid="28"/>
+ <tile gid="25"/>
+ <tile gid="30"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="29"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="18"/>
+ <tile gid="18"/>
+ <tile gid="18"/>
+ <tile gid="18"/>
+ <tile gid="18"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="19"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ </data>
+ </layer>
+ <imagelayer name="Image Layer 1" x="100" y="100">
+ <image source="hills.png"/>
+ </imagelayer>
+ <layer name="Tile Layer 1" width="20" height="10">
+ <properties>
+ <property name="customlayerprop" value="1"/>
+ <property name="customlayerprop2" value="2"/>
+ </properties>
+ <data>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="14"/>
+ <tile gid="15"/>
+ <tile gid="15"/>
+ <tile gid="16"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="14"/>
+ <tile gid="15"/>
+ <tile gid="16"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="24"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="1"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="3"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="1"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="4"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="7"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="4"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="2"/>
+ <tile gid="8"/>
+ <tile gid="9"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="7"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="1"/>
+ <tile gid="2"/>
+ <tile gid="8"/>
+ <tile gid="9"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="7"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="4"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="7"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="0"/>
+ <tile gid="4"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ <tile gid="6"/>
+ </data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/template.tx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/template.tx
new file mode 100644
index 0000000..1f00d19
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/template.tx
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<template>
+ <object width="30.9247" height="20.7597"/>
+</template>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-object-layer.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-object-layer.tmx
new file mode 100644
index 0000000..dcdbee9
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-object-layer.tmx
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="right-down" width="25" height="15" tilewidth="64" tileheight="64" nextobjectid="12">
+ <objectgroup name="Object Layer 1">
+ <object id="1" x="131.345" y="65.234" width="311.111" height="311.232">
+ <properties>
+ <property name="shape" value="circle"/>
+ </properties>
+ <ellipse/>
+ </object>
+ <object id="7" class="sprite" x="240" y="440" width="322" height="186" visible="0"/>
+ <object id="8" type="rectangle" x="506" y="142" width="136" height="234">
+ <properties>
+ <property name="area" value="player-spawn"/>
+ </properties>
+ </object>
+ <object id="9" name="polygon" x="621" y="450">
+ <polygon points="0,0 180,90 -8,275 -45,81 38,77"/>
+ </object>
+ <object id="11" x="43" y="350">
+ <polyline points="0,0 28,299 326,413 461,308"/>
+ </object>
+ <object id="12" gid="23" x="169.333" y="490.909" width="345.818" height="364"/>
+ </objectgroup>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-base64.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-base64.tmx
new file mode 100644
index 0000000..09177cb
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-base64.tmx
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="left-down" width="3" height="3" tilewidth="32" tileheight="32" nextobjectid="1">
+ <tileset firstgid="1" name="test-tileset" tilewidth="32" tileheight="32" spacing="2" margin="2">
+ <image source="test-tileset.png" width="104" height="104"/>
+ </tileset>
+ <layer name="Tile Layer 1" width="3" height="3">
+ <data encoding="base64">
+ AQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA
+ </data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-csv.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-csv.tmx
new file mode 100644
index 0000000..b709f2b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-csv.tmx
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="left-down" width="3" height="3" tilewidth="32" tileheight="32" nextobjectid="1">
+ <tileset firstgid="1" name="test-tileset" tilewidth="32" tileheight="32" spacing="2" margin="2">
+ <image source="test-tileset.png" width="104" height="104"/>
+ </tileset>
+ <layer name="Tile Layer 1" width="3" height="3">
+ <data encoding="csv">
+1,2,3,
+4,5,6,
+7,8,9
+</data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-gzip.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-gzip.tmx
new file mode 100644
index 0000000..c48705f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-gzip.tmx
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="left-down" width="3" height="3" tilewidth="32" tileheight="32" nextobjectid="1">
+ <tileset firstgid="1" name="test-tileset" tilewidth="32" tileheight="32" spacing="2" margin="2">
+ <image source="test-tileset.png" width="104" height="104"/>
+ </tileset>
+ <layer name="Tile Layer 1" width="3" height="3">
+ <data encoding="base64" compression="gzip">
+ H4sIAAAAAAAACw3Dhw0AAAjDsLLh/4eJJZskZzBZbA6Xxwdm9rUOJAAAAA==
+ </data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-xml.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-xml.tmx
new file mode 100644
index 0000000..0f1a741
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-xml.tmx
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="left-down" width="3" height="3" tilewidth="32" tileheight="32" nextobjectid="1">
+ <tileset firstgid="1" name="test-tileset" tilewidth="32" tileheight="32" spacing="2" margin="2">
+ <image source="test-tileset.png" width="104" height="104"/>
+ </tileset>
+ <layer name="Tile Layer 1" width="3" height="3">
+ <data>
+ <tile gid="1"/>
+ <tile gid="2"/>
+ <tile gid="3"/>
+ <tile gid="4"/>
+ <tile gid="5"/>
+ <tile gid="6"/>
+ <tile gid="7"/>
+ <tile gid="8"/>
+ <tile gid="9"/>
+ </data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-zlib.tmx b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-zlib.tmx
new file mode 100644
index 0000000..7a5a548
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TestData/test-tileset-zlib.tmx
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.0" orientation="orthogonal" renderorder="left-down" width="3" height="3" tilewidth="32" tileheight="32" nextobjectid="1">
+ <tileset firstgid="1" name="test-tileset" tilewidth="32" tileheight="32" spacing="2" margin="2">
+ <image source="test-tileset.png" width="104" height="104"/>
+ </tileset>
+ <layer name="Tile Layer 1" width="3" height="3">
+ <data encoding="base64" compression="zlib">
+ eJwNw4cNAAAIw7Cy4f+HiSWbJGcwWWwOl8cHArgALg==
+ </data>
+ </layer>
+</map>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TiledMapImporterProcessorTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TiledMapImporterProcessorTests.cs
new file mode 100644
index 0000000..352457a
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/TiledMapImporterProcessorTests.cs
@@ -0,0 +1,205 @@
+using System.IO;
+using System.Linq;
+using Microsoft.Xna.Framework.Content.Pipeline;
+using MonoGame.Extended.Content.Pipeline.Tiled;
+using MonoGame.Extended.Tiled.Serialization;
+using NSubstitute;
+using Xunit;
+
+namespace MonoGame.Extended.Content.Pipeline.Tests.Tiled
+{
+
+ public class TiledMapImporterProcessorTests
+ {
+ [Fact]
+ public void TiledMapImporter_Import_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "level01.tmx");
+
+ var logger = Substitute.For<ContentBuildLogger>();
+ var importer = new TiledMapImporter();
+ var importerContext = Substitute.For<ContentImporterContext>();
+ importerContext.Logger.Returns(logger);
+
+ var contentItem = importer.Import(filePath, importerContext);
+ var map = contentItem.Data;
+
+ Assert.Equal("1.0", map.Version);
+ Assert.Equal(TiledMapOrientationContent.Orthogonal, map.Orientation);
+ Assert.Equal(TiledMapTileDrawOrderContent.RightDown, map.RenderOrder);
+ Assert.Equal(20, map.Width);
+ Assert.Equal(10, map.Height);
+ Assert.Equal(128, map.TileWidth);
+ Assert.Equal(128, map.TileHeight);
+ Assert.Equal("#7d7d7d", map.BackgroundColor);
+ Assert.Equal("awesome", map.Properties[0].Name);
+ Assert.Equal("42", map.Properties[0].Value);
+ Assert.Single(map.Tilesets);
+ Assert.Equal(3, map.Layers.Count);
+ Assert.Equal(TiledMapOrientationContent.Orthogonal, map.Orientation);
+
+ var tileset = map.Tilesets.First();
+ Assert.Equal(1, tileset.FirstGlobalIdentifier);
+ Assert.Equal("free-tileset.png", Path.GetFileName(tileset.Image.Source));
+ Assert.Equal(652, tileset.Image.Width);
+ Assert.Equal(783, tileset.Image.Height);
+ Assert.Equal(2, tileset.Margin);
+ Assert.Equal(30, tileset.TileCount);
+ Assert.Equal("free-tileset", tileset.Name);
+ Assert.Null(tileset.Source);
+ Assert.Equal(2, tileset.Spacing);
+ //Assert.Equal(0, tileset.TerrainTypes.Count);
+ Assert.Empty(tileset.Properties);
+ Assert.Equal(128, tileset.TileHeight);
+ Assert.Equal(128, tileset.TileWidth);
+ Assert.Equal(0, tileset.TileOffset.X);
+ Assert.Equal(0, tileset.TileOffset.Y);
+
+ var tileLayer2 = (TiledMapTileLayerContent)map.Layers[0];
+ Assert.Equal("Tile Layer 2", tileLayer2.Name);
+ Assert.Equal(1, tileLayer2.Opacity);
+ Assert.Empty(tileLayer2.Properties);
+ Assert.True(tileLayer2.Visible);
+ Assert.Equal(200, tileLayer2.Data.Tiles.Count);
+ Assert.Equal(0, tileLayer2.X);
+ Assert.Equal(0, tileLayer2.Y);
+
+ var imageLayer = (TiledMapImageLayerContent)map.Layers[1];
+ Assert.Equal("Image Layer 1", imageLayer.Name);
+ Assert.Equal(1, imageLayer.Opacity);
+ Assert.Empty(imageLayer.Properties);
+ Assert.True(imageLayer.Visible);
+ Assert.Equal("hills.png", Path.GetFileName(imageLayer.Image.Source));
+ Assert.Equal(100, imageLayer.X);
+ Assert.Equal(100, imageLayer.Y);
+
+ var tileLayer1 = (TiledMapTileLayerContent)map.Layers[2];
+ Assert.Equal("Tile Layer 1", tileLayer1.Name);
+ Assert.Equal(2, tileLayer1.Properties.Count);
+
+ Assert.Equal("customlayerprop", tileLayer1.Properties[0].Name);
+ Assert.Equal("1", tileLayer1.Properties[0].Value);
+
+ Assert.Equal("customlayerprop2", tileLayer1.Properties[1].Name);
+ Assert.Equal("2", tileLayer1.Properties[1].Value);
+ }
+
+ [Fact]
+ public void TiledMapImporter_Xml_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-tileset-xml.tmx");
+ var map = ImportAndProcessMap(filePath);
+ var layer = map.Layers.OfType<TiledMapTileLayerContent>().First();
+ var actualData = layer.Data.Tiles.Select(i => i.GlobalIdentifier).ToArray();
+
+ Assert.Null(layer.Data.Encoding);
+ Assert.Null(layer.Data.Compression);
+ Assert.True(new uint[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.SequenceEqual(actualData));
+ }
+
+ [Fact]
+ public void TiledMapImporter_Csv_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-tileset-csv.tmx");
+ var map = ImportAndProcessMap(filePath);
+ var layer = map.Layers.OfType<TiledMapTileLayerContent>().First();
+ var data = layer.Data.Tiles.Select(i => i.GlobalIdentifier).ToArray();
+
+ Assert.Equal("csv", layer.Data.Encoding);
+ Assert.Null(layer.Data.Compression);
+ //Assert.True(new uint[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.SequenceEqual(data));
+ }
+
+ [Fact]
+ public void TiledMapImporter_Base64_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-tileset-base64.tmx");
+ var map = ImportAndProcessMap(filePath);
+ var layer = map.Layers.OfType<TiledMapTileLayerContent>().First();
+ var data = layer.Data.Tiles.Select(i => i.GlobalIdentifier).ToArray();
+
+ Assert.Equal("base64", layer.Data.Encoding);
+ Assert.Null(layer.Data.Compression);
+ //Assert.True(new uint[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.SequenceEqual(data));
+ }
+
+ [Fact]
+ public void TiledMapImporter_Gzip_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-tileset-gzip.tmx");
+ var map = ImportAndProcessMap(filePath);
+ var layer = map.Layers.OfType<TiledMapTileLayerContent>().First();
+ var data = layer.Data.Tiles.Select(i => i.GlobalIdentifier).ToArray();
+
+ Assert.Equal("base64", layer.Data.Encoding);
+ Assert.Equal("gzip", layer.Data.Compression);
+ //Assert.True(new uint[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.SequenceEqual(data));
+ }
+
+
+ [Fact]
+ public void TiledMapImporter_Zlib_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-tileset-zlib.tmx");
+ var map = ImportAndProcessMap(filePath);
+ var layer = map.Layers.OfType<TiledMapTileLayerContent>().First();
+ var data = layer.Data.Tiles.Select(i => i.GlobalIdentifier).ToArray();
+
+ Assert.Equal("base64", layer.Data.Encoding);
+ Assert.Equal("zlib", layer.Data.Compression);
+ //Assert.True(new uint[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.SequenceEqual(data));
+ }
+
+ [Fact]
+ public void TiledMapImporter_ObjectLayer_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "test-object-layer.tmx");
+ var map = ImportAndProcessMap(filePath);
+
+ Assert.Single(map.Layers);
+ Assert.IsType<TiledMapObjectLayerContent>(map.Layers[0]);
+ var tmxObjectGroup = map.Layers[0] as TiledMapObjectLayerContent;
+ var tmxObject = tmxObjectGroup.Objects[0];
+ var tmxPolygon = tmxObjectGroup.Objects[3].Polygon;
+ var tmxPolyline = tmxObjectGroup.Objects[4].Polyline;
+
+ Assert.Equal("Object Layer 1", tmxObjectGroup.Name);
+ Assert.Equal(1, tmxObject.Identifier);
+ Assert.Equal(131.345f, tmxObject.X);
+ Assert.Equal(65.234f, tmxObject.Y);
+ Assert.Equal(311.111f, tmxObject.Width);
+ Assert.Equal(311.232f, tmxObject.Height);
+ Assert.Single(tmxObject.Properties);
+ Assert.Equal("shape", tmxObject.Properties[0].Name);
+ Assert.Equal("circle", tmxObject.Properties[0].Value);
+ Assert.NotNull(tmxObject.Ellipse);
+ Assert.False(tmxObjectGroup.Objects[1].Visible);
+ Assert.Equal((uint)0, tmxObjectGroup.Objects[1].GlobalIdentifier);
+ Assert.Equal((uint)23, tmxObjectGroup.Objects[5].GlobalIdentifier);
+ Assert.Equal("rectangle", tmxObjectGroup.Objects[2].Type);
+ Assert.Equal("sprite", tmxObjectGroup.Objects[1].Class);
+ Assert.NotNull(tmxPolygon);
+ Assert.Equal("0,0 180,90 -8,275 -45,81 38,77", tmxPolygon.Points);
+ Assert.NotNull(tmxPolyline);
+ Assert.Equal("0,0 28,299 326,413 461,308", tmxPolyline.Points);
+ }
+
+
+ private static TiledMapContent ImportAndProcessMap(string filename)
+ {
+ var logger = Substitute.For<ContentBuildLogger>();
+ var importer = new TiledMapImporter();
+ var importerContext = Substitute.For<ContentImporterContext>();
+ importerContext.Logger.Returns(logger);
+
+ var processor = new TiledMapProcessor();
+ var processorContext = Substitute.For<ContentProcessorContext>();
+ processorContext.Logger.Returns(logger);
+
+ var import = importer.Import(filename, importerContext);
+ var result = processor.Process(import, processorContext);
+
+ return result.Data;
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorImporterTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorImporterTests.cs
new file mode 100644
index 0000000..c3af651
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorImporterTests.cs
@@ -0,0 +1,34 @@
+using System;
+using System.IO;
+using MonoGame.Extended.Content.Pipeline.Animations;
+using Xunit;
+
+namespace MonoGame.Extended.Content.Pipeline.Tests
+{
+
+ public class AstridAnimatorImporterTests
+ {
+ [Fact]
+ public void AstridAnimatorImporter_Import_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "astrid-animator.aa");
+ var importer = new AstridAnimatorImporter();
+ var result = importer.Import(filePath, null);
+ var data = result.Data;
+
+ Assert.Equal("astrid-animator-atlas.json", data.TextureAtlas);
+ Assert.Equal(2, data.Animations.Count);
+
+ Assert.Equal("appear", data.Animations[0].Name);
+ Assert.Equal(8, data.Animations[0].FramesPerSecond);
+ Assert.Equal(2, data.Animations[0].Frames.Count);
+ Assert.Equal("appear_01", data.Animations[0].Frames[0]);
+ Assert.Equal("appear_02", data.Animations[0].Frames[1]);
+
+ Assert.Equal("die", data.Animations[1].Name);
+ Assert.Equal(16, data.Animations[1].FramesPerSecond);
+ Assert.Single(data.Animations[1].Frames);
+ Assert.Equal("die_01", data.Animations[1].Frames[0]);
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorProcessorTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorProcessorTests.cs
new file mode 100644
index 0000000..0a27412
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/AstridAnimatorProcessorTests.cs
@@ -0,0 +1,30 @@
+using System;
+using System.IO;
+using MonoGame.Extended.Content.Pipeline.Animations;
+using Xunit;
+
+namespace MonoGame.Extended.Content.Pipeline.Tests
+{
+
+ public class AstridAnimatorProcessorTests
+ {
+ [Fact]
+ public void AstridAnimatorProcessor_Process_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath("TestData", "astrid-animator.aa");
+
+ var importer = new AstridAnimatorImporter();
+ var importerResult = importer.Import(filePath, null);
+
+ var processor = new AstridAnimatorProcessor();
+ var result = processor.Process(importerResult, null);
+
+ Assert.Equal("astrid-animator-atlas", result.TextureAtlasAssetName);
+ Assert.Equal("TestData", Path.GetFileName(result.Directory));
+ Assert.Equal(3, result.Frames.Count);
+ Assert.Equal("appear_01", result.Frames[0]);
+ Assert.Equal("appear_02", result.Frames[1]);
+ Assert.Equal("die_01", result.Frames[2]);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj
new file mode 100644
index 0000000..cee444e
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <PackageReference Include="MonoGame.Framework.Content.Pipeline" Version="3.8.1.303" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Content.Pipeline\MonoGame.Extended.Content.Pipeline.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Update="TestData\astrid-animator-atlas.json" CopyToOutputDirectory="PreserveNewest" />
+ <None Update="TestData\astrid-animator.aa" CopyToOutputDirectory="PreserveNewest" />
+ <None Update="TestData\test-tileset.json" CopyToOutputDirectory="PreserveNewest" />
+ </ItemGroup>
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator-atlas.json b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator-atlas.json
new file mode 100644
index 0000000..7a9f9f0
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator-atlas.json
@@ -0,0 +1,390 @@
+{
+ "frames": [
+ {
+ "filename": "appear_01.png",
+ "frame": {"x":494,"y":111,"w":27,"h":31},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":52,"y":113,"w":27,"h":31},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_02.png",
+ "frame": {"x":465,"y":111,"w":27,"h":42},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":52,"y":102,"w":27,"h":42},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_03.png",
+ "frame": {"x":705,"y":158,"w":31,"h":52},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":41,"y":92,"w":31,"h":52},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_04.png",
+ "frame": {"x":112,"y":298,"w":74,"h":17},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":16,"y":127,"w":74,"h":17},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_05.png",
+ "frame": {"x":564,"y":621,"w":94,"h":62},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":16,"y":82,"w":94,"h":62},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_06.png",
+ "frame": {"x":507,"y":544,"w":106,"h":75},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":69,"w":106,"h":75},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_07.png",
+ "frame": {"x":119,"y":637,"w":98,"h":93},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":51,"w":98,"h":93},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_08.png",
+ "frame": {"x":486,"y":621,"w":76,"h":118},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":12,"y":26,"w":76,"h":118},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_09.png",
+ "frame": {"x":321,"y":601,"w":73,"h":124},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":15,"y":20,"w":73,"h":124},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_10.png",
+ "frame": {"x":406,"y":600,"w":78,"h":140},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":10,"y":4,"w":78,"h":140},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "appear_11.png",
+ "frame": {"x":615,"y":469,"w":82,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":11,"y":0,"w":82,"h":144},
+ "sourceSize": {"w":110,"h":144},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_01.png",
+ "frame": {"x":523,"y":15,"w":112,"h":144},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":38,"y":0,"w":112,"h":144},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_02.png",
+ "frame": {"x":113,"y":2,"w":134,"h":129},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":32,"y":15,"w":134,"h":129},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_03.png",
+ "frame": {"x":249,"y":2,"w":139,"h":106},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":36,"y":38,"w":139,"h":106},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_04.png",
+ "frame": {"x":465,"y":161,"w":133,"h":73},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":44,"y":71,"w":133,"h":73},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_05.png",
+ "frame": {"x":113,"y":133,"w":147,"h":69},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":42,"y":76,"w":147,"h":69},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_06.png",
+ "frame": {"x":564,"y":685,"w":154,"h":51},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":41,"y":94,"w":154,"h":51},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_07.png",
+ "frame": {"x":242,"y":266,"w":188,"h":24},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":28,"y":121,"w":188,"h":24},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "die_08.png",
+ "frame": {"x":390,"y":2,"w":222,"h":11},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":134,"w":222,"h":11},
+ "sourceSize": {"w":222,"h":146},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_01.png",
+ "frame": {"x":637,"y":2,"w":100,"h":154},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":100,"h":154},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_02.png",
+ "frame": {"x":262,"y":110,"w":100,"h":154},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":100,"h":154},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_03.png",
+ "frame": {"x":364,"y":111,"w":99,"h":153},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":2,"w":99,"h":153},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_04.png",
+ "frame": {"x":203,"y":298,"w":99,"h":153},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":2,"w":99,"h":153},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_05.png",
+ "frame": {"x":305,"y":447,"w":99,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":3,"w":99,"h":152},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_06.png",
+ "frame": {"x":405,"y":292,"w":98,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":2,"y":3,"w":98,"h":152},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_07.png",
+ "frame": {"x":304,"y":292,"w":99,"h":153},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":2,"w":99,"h":153},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_08.png",
+ "frame": {"x":103,"y":480,"w":99,"h":155},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":99,"h":155},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_09.png",
+ "frame": {"x":2,"y":322,"w":99,"h":156},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":99,"h":156},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "go_10.png",
+ "frame": {"x":2,"y":480,"w":99,"h":156},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":0,"w":99,"h":156},
+ "sourceSize": {"w":100,"h":156},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_01.png",
+ "frame": {"x":2,"y":2,"w":109,"h":159},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":77,"y":0,"w":109,"h":159},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_02.png",
+ "frame": {"x":103,"y":322,"w":98,"h":156},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":79,"y":3,"w":98,"h":156},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_03.png",
+ "frame": {"x":2,"y":163,"w":108,"h":157},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":25,"y":3,"w":108,"h":157},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_04.png",
+ "frame": {"x":390,"y":15,"w":131,"h":94},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":66,"w":131,"h":94},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_05.png",
+ "frame": {"x":112,"y":204,"w":128,"h":92},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":3,"y":70,"w":128,"h":92},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_06.png",
+ "frame": {"x":2,"y":638,"w":115,"h":100},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":16,"y":60,"w":115,"h":100},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "hit_07.png",
+ "frame": {"x":219,"y":608,"w":100,"h":132},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":32,"y":28,"w":100,"h":132},
+ "sourceSize": {"w":186,"h":162},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_01.png",
+ "frame": {"x":204,"y":453,"w":99,"h":153},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":0,"w":99,"h":153},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_02.png",
+ "frame": {"x":406,"y":446,"w":99,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":99,"h":152},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_03.png",
+ "frame": {"x":505,"y":236,"w":98,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":98,"h":152},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_04.png",
+ "frame": {"x":507,"y":390,"w":98,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":98,"h":152},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_05.png",
+ "frame": {"x":605,"y":161,"w":98,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":1,"y":1,"w":98,"h":152},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ },
+ {
+ "filename": "idle_06.png",
+ "frame": {"x":607,"y":315,"w":99,"h":152},
+ "rotated": false,
+ "trimmed": true,
+ "spriteSourceSize": {"x":0,"y":1,"w":99,"h":152},
+ "sourceSize": {"w":100,"h":154},
+ "pivot": {"x":0.5,"y":1}
+ }],
+ "meta": {
+ "app": "http://www.codeandweb.com/texturepacker",
+ "version": "1.0",
+ "image": "zombie.png",
+ "format": "RGBA8888",
+ "size": {"w":742,"h":742},
+ "scale": "0.5",
+ "smartupdate": "$TexturePacker:SmartUpdate:28fca4a18eeef90b2646ccc59eb1f593:ccb2bd5648fe15c740c647d39945c765:3e6ed6fe54c801c395eefb25aa5e45e8$"
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator.aa b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator.aa
new file mode 100644
index 0000000..ba8d43f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/astrid-animator.aa
@@ -0,0 +1,15 @@
+{
+ "TextureAtlas": "astrid-animator-atlas.json",
+ "Animations": [
+ {
+ "Name": "appear",
+ "FramesPerSecond": 8,
+ "Frames": [ "appear_01", "appear_02" ]
+ },
+ {
+ "Name": "die",
+ "FramesPerSecond": 16,
+ "Frames": [ "die_01" ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/test-tileset.json b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/test-tileset.json
new file mode 100644
index 0000000..57e46f5
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TestData/test-tileset.json
@@ -0,0 +1,93 @@
+{"frames": [
+
+{
+ "filename": "1.png",
+ "frame": {"x":2,"y":2,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "2.png",
+ "frame": {"x":36,"y":2,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "3.png",
+ "frame": {"x":70,"y":2,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "4.png",
+ "frame": {"x":2,"y":36,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "5.png",
+ "frame": {"x":36,"y":36,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "6.png",
+ "frame": {"x":70,"y":36,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "7.png",
+ "frame": {"x":2,"y":70,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "8.png",
+ "frame": {"x":36,"y":70,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+},
+{
+ "filename": "9.png",
+ "frame": {"x":70,"y":70,"w":32,"h":32},
+ "rotated": false,
+ "trimmed": false,
+ "spriteSourceSize": {"x":0,"y":0,"w":32,"h":32},
+ "sourceSize": {"w":32,"h":32},
+ "pivot": {"x":0.5,"y":0.5}
+}],
+"meta": {
+ "app": "http://www.codeandweb.com/texturepacker",
+ "version": "1.0",
+ "image": "test-tileset.png",
+ "format": "RGBA8888",
+ "size": {"w":104,"h":104},
+ "scale": "1",
+ "smartupdate": "$TexturePacker:SmartUpdate:f5f4c00eb32fae603057f0d9dc5c7b73:ca39697f48630ecdea6d81a8fdc48cf6:c79a4cc8e4ba9657462e67dafcaf93d2$"
+}
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TexturePackerJsonImporterProcessorTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TexturePackerJsonImporterProcessorTests.cs
new file mode 100644
index 0000000..4172ee2
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Content.Pipeline.Tests/TexturePackerJsonImporterProcessorTests.cs
@@ -0,0 +1,33 @@
+using Microsoft.Xna.Framework.Content.Pipeline;
+using MonoGame.Extended.Content.Pipeline.TextureAtlases;
+using NSubstitute;
+using Xunit;
+
+namespace MonoGame.Extended.Content.Pipeline.Tests
+{
+
+ public class TexturePackerJsonImporterProcessorTests
+ {
+ [Fact]
+ public void TexturePackerJsonImporter_Import_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath(@"TestData/test-tileset.json");
+ var importer = new TexturePackerJsonImporter();
+ var data = importer.Import(filePath, Substitute.For<ContentImporterContext>());
+
+ Assert.NotNull(data);
+ }
+
+ [Fact]
+ public void TexturePackerJsonImporter_Processor_Test()
+ {
+ var filePath = PathExtensions.GetApplicationFullPath(@"TestData/test-tileset.json");
+ var importer = new TexturePackerJsonImporter();
+ var input = importer.Import(filePath, Substitute.For<ContentImporterContext>());
+ var processor = new TexturePackerProcessor();
+ var output = processor.Process(input, Substitute.For<ContentProcessorContext>());
+
+ Assert.NotNull(output);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectBuilderTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectBuilderTests.cs
new file mode 100644
index 0000000..1b3e971
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectBuilderTests.cs
@@ -0,0 +1,70 @@
+using System;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Sprites;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ public class AspectBuilderTests
+ {
+ [Fact]
+ public void MatchAllTypes()
+ {
+ var builder = new AspectBuilder()
+ .All(typeof(Transform2), typeof(Sprite));
+
+ Assert.Equal(2, builder.AllTypes.Count);
+ Assert.Contains(typeof(Transform2), builder.AllTypes);
+ Assert.Contains(typeof(Sprite), builder.AllTypes);
+ }
+
+ [Fact]
+ public void MatchAllTypesIsEmpty()
+ {
+ var builder = new AspectBuilder()
+ .All();
+
+ Assert.Empty(builder.AllTypes);
+ Assert.Empty(builder.OneTypes);
+ Assert.Empty(builder.ExclusionTypes);
+ }
+
+ [Fact]
+ public void MatchOneOfType()
+ {
+ var builder = new AspectBuilder()
+ .One(typeof(Transform2), typeof(Sprite));
+
+ Assert.Equal(2, builder.OneTypes.Count);
+ Assert.Contains(typeof(Transform2), builder.OneTypes);
+ Assert.Contains(typeof(Sprite), builder.OneTypes);
+ }
+
+ [Fact]
+ public void ExcludeTypes()
+ {
+ var builder = new AspectBuilder()
+ .Exclude(typeof(Transform2), typeof(Sprite));
+
+ Assert.Equal(2, builder.ExclusionTypes.Count);
+ Assert.Contains(typeof(Transform2), builder.ExclusionTypes);
+ Assert.Contains(typeof(Sprite), builder.ExclusionTypes);
+ }
+
+ [Fact]
+ public void BuildAspect()
+ {
+ var componentManager = new ComponentManager();
+ var builder = new AspectBuilder()
+ .All(typeof(Transform2), typeof(Sprite))
+ .One(typeof(string))
+ .Exclude(typeof(Texture2D));
+
+ var aspect = builder.Build(componentManager);
+
+ Assert.True(aspect.AllSet.Data != 0);
+ Assert.True(aspect.OneSet.Data != 0);
+ Assert.True(aspect.ExclusionSet.Data != 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectTests.cs
new file mode 100644
index 0000000..b06780b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/AspectTests.cs
@@ -0,0 +1,88 @@
+using System.Collections.Specialized;
+using MonoGame.Extended.Sprites;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ public class DummyComponent
+ {
+ }
+
+ public class AspectTests
+ {
+ private readonly ComponentManager _componentManager;
+ private readonly BitVector32 _entityA;
+ private readonly BitVector32 _entityB;
+
+ public AspectTests()
+ {
+ _componentManager = new ComponentManager();
+ _entityA = new BitVector32
+ {
+ [1 << _componentManager.GetComponentTypeId(typeof(Transform2))] = true,
+ [1 << _componentManager.GetComponentTypeId(typeof(Sprite))] = true,
+ [1 << _componentManager.GetComponentTypeId(typeof(DummyComponent))] = true
+ };
+ _entityB = new BitVector32
+ {
+ [1 << _componentManager.GetComponentTypeId(typeof(Transform2))] = true,
+ [1 << _componentManager.GetComponentTypeId(typeof(Sprite))] = true,
+ };
+ }
+
+ [Fact]
+ public void EmptyAspectMatchesAllComponents()
+ {
+ var componentManager = new ComponentManager();
+ var emptyAspect = Aspect.All()
+ .Build(componentManager);
+
+ Assert.True(emptyAspect.IsInterested(_entityA));
+ Assert.True(emptyAspect.IsInterested(_entityB));
+ }
+
+ [Fact]
+ public void IsInterestedInAllComponents()
+ {
+ var allAspect = Aspect
+ .All(typeof(Sprite), typeof(Transform2), typeof(DummyComponent))
+ .Build(_componentManager);
+
+ Assert.True(allAspect.IsInterested(_entityA));
+ Assert.False(allAspect.IsInterested(_entityB));
+ }
+
+ [Fact]
+ public void IsInterestedInEitherOneOfTheComponents()
+ {
+ var eitherOneAspect = Aspect
+ .One(typeof(Transform2), typeof(DummyComponent))
+ .Build(_componentManager);
+
+ Assert.True(eitherOneAspect.IsInterested(_entityA));
+ Assert.True(eitherOneAspect.IsInterested(_entityB));
+ }
+
+ [Fact]
+ public void IsInterestedInJustOneComponent()
+ {
+ var oneAspect = Aspect
+ .One(typeof(DummyComponent))
+ .Build(_componentManager);
+
+ Assert.True(oneAspect.IsInterested(_entityA));
+ Assert.False(oneAspect.IsInterested(_entityB));
+ }
+
+ [Fact]
+ public void IsInterestedInExcludingOneComponent()
+ {
+ var oneAspect = Aspect
+ .Exclude(typeof(DummyComponent))
+ .Build(_componentManager);
+
+ Assert.False(oneAspect.IsInterested(_entityA));
+ Assert.True(oneAspect.IsInterested(_entityB));
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/BitArrayExtensionsTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/BitArrayExtensionsTests.cs
new file mode 100644
index 0000000..44ec4af
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/BitArrayExtensionsTests.cs
@@ -0,0 +1,20 @@
+using System.Collections;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ public class BitArrayExtensionsTests
+ {
+ [Fact]
+ public void BitArrayIsEmpty()
+ {
+ Assert.True(new BitArray(1).IsEmpty());
+ Assert.False(new BitArray(new[] { true }).IsEmpty());
+ Assert.True(new BitArray(new[] { false }).IsEmpty());
+
+ var bitArray = new BitArray(new[] { true });
+ bitArray.Set(0, false);
+ Assert.True(bitArray.IsEmpty());
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs
new file mode 100644
index 0000000..8d988e4
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentManagerTests.cs
@@ -0,0 +1,47 @@
+using MonoGame.Extended.Sprites;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ public class ComponentManagerTests
+ {
+ [Fact]
+ public void GetMapperForType()
+ {
+ var componentManager = new ComponentManager();
+ var transformMapper = componentManager.GetMapper<Transform2>();
+ var spriteMapper = componentManager.GetMapper<Sprite>();
+
+ Assert.IsType<ComponentMapper<Transform2>>(transformMapper);
+ Assert.IsType<ComponentMapper<Sprite>>(spriteMapper);
+ Assert.Equal(0, transformMapper.Id);
+ Assert.Equal(1, spriteMapper.Id);
+ Assert.Same(spriteMapper, componentManager.GetMapper<Sprite>());
+ }
+
+ [Fact]
+ public void GetComponentTypeId()
+ {
+ var componentManager = new ComponentManager();
+
+ Assert.Equal(0, componentManager.GetComponentTypeId(typeof(Transform2)));
+ Assert.Equal(1, componentManager.GetComponentTypeId(typeof(Sprite)));
+ Assert.Equal(0, componentManager.GetComponentTypeId(typeof(Transform2)));
+ }
+
+ //[Fact]
+ //public void GetCompositionIdentity()
+ //{
+ // var compositionBits = new BitArray(3)
+ // {
+ // [0] = true,
+ // [1] = false,
+ // [2] = true
+ // };
+ // var componentManager = new ComponentManager();
+ // var identity = componentManager.GetCompositionIdentity(compositionBits);
+
+ // Assert.Equal(0b101, identity);
+ //}
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentMapperTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentMapperTests.cs
new file mode 100644
index 0000000..54fab04
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentMapperTests.cs
@@ -0,0 +1,95 @@
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ public class ComponentMapperTests
+ {
+ [Fact]
+ public void CreateComponentMapper()
+ {
+ var mapper = new ComponentMapper<object>(0, _ => {});
+
+ Assert.Equal(typeof(object), mapper.ComponentType);
+ Assert.Empty(mapper.Components);
+ }
+
+ [Fact]
+ public void OnPut()
+ {
+ const int entityId = 3;
+
+ var mapper = new ComponentMapper<Transform2>(1, _ => { });
+ var component = new Transform2();
+
+ mapper.OnPut += (entId) =>
+ {
+ Assert.Equal(entityId, entId);
+ Assert.Same(component, mapper.Get(entityId));
+ };
+
+ mapper.Put(entityId, component);
+ }
+
+ [Fact]
+ public void PutAndGetComponent()
+ {
+ const int entityId = 3;
+
+ var mapper = new ComponentMapper<Transform2>(1, _ => { });
+ var component = new Transform2();
+
+ mapper.Put(entityId, component);
+
+ Assert.Equal(typeof(Transform2), mapper.ComponentType);
+ Assert.True(mapper.Components.Count >= 1);
+ Assert.Same(component, mapper.Get(entityId));
+ }
+
+ [Fact]
+ public void OnDelete()
+ {
+ const int entityId = 1;
+
+ var mapper = new ComponentMapper<Transform2>(2, _ => { });
+ var component = new Transform2();
+
+ mapper.OnDelete += (entId) =>
+ {
+ Assert.Equal(entityId, entId);
+ Assert.False(mapper.Has(entityId));
+ };
+
+ mapper.Put(entityId, component);
+ mapper.Delete(entityId);
+ }
+
+ [Fact]
+ public void DeleteComponent()
+ {
+ const int entityId = 1;
+
+ var mapper = new ComponentMapper<Transform2>(2, _ => { });
+ var component = new Transform2();
+
+ mapper.Put(entityId, component);
+ mapper.Delete(entityId);
+
+ Assert.False(mapper.Has(entityId));
+ }
+
+ [Fact]
+ public void HasComponent()
+ {
+ const int entityId = 0;
+
+ var mapper = new ComponentMapper<Transform2>(3, _ => { });
+ var component = new Transform2();
+
+ Assert.False(mapper.Has(entityId));
+
+ mapper.Put(entityId, component);
+
+ Assert.True(mapper.Has(entityId));
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentTypeTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentTypeTests.cs
new file mode 100644
index 0000000..8953804
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/ComponentTypeTests.cs
@@ -0,0 +1,20 @@
+using MonoGame.Extended.Sprites;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests
+{
+ //public class ComponentTypeTests
+ //{
+ // [Fact]
+ // public void CreateComponentType()
+ // {
+ // var type = typeof(Sprite);
+ // var componentType = new ComponentType(type, 3);
+
+ // Assert.Same(type, componentType.Type);
+ // Assert.Equal(3, componentType.Id);
+ // Assert.Equal(new ComponentType(typeof(Sprite), 3), componentType);
+ // Assert.Equal(componentType.Id, componentType.GetHashCode());
+ // }
+ //}
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj
new file mode 100644
index 0000000..9eac8c4
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Entities\MonoGame.Extended.Entities.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/WorldManagerTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/WorldManagerTests.cs
new file mode 100644
index 0000000..7620f1e
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Entities.Tests/WorldManagerTests.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities.Systems;
+using MonoGame.Extended.Sprites;
+using Xunit;
+
+namespace MonoGame.Extended.Entities.Tests;
+
+public class WorldManagerTests
+{
+ private GameTime _gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromMilliseconds(16));
+
+ [Fact]
+ public void CrudEntity()
+ {
+ var dummySystem = new DummySystem();
+ var worldBuilder = new WorldBuilder();
+ worldBuilder.AddSystem(dummySystem);
+ var world = worldBuilder.Build();
+
+ world.Initialize();
+
+ var entity = world.CreateEntity();
+ entity.Attach(new Transform2());
+ world.Update(_gameTime);
+ world.Draw(_gameTime);
+ var otherEntity = world.GetEntity(entity.Id);
+ Assert.Equal(entity, otherEntity);
+ Assert.True(otherEntity.Has<Transform2>());
+ Assert.Contains(entity.Id, dummySystem.AddedEntitiesId);
+
+ entity.Destroy();
+ world.Update(_gameTime);
+ world.Draw(_gameTime);
+ otherEntity = world.GetEntity(entity.Id);
+ Assert.Null(otherEntity);
+ Assert.Contains(entity.Id, dummySystem.RemovedEntitiesId);
+ }
+
+ private class DummyComponent { }
+
+ private class DummySystem : EntitySystem, IUpdateSystem, IDrawSystem
+ {
+ public List<int> AddedEntitiesId { get; } = new ();
+ public List<int> RemovedEntitiesId { get; } = new ();
+
+ public DummySystem() : base(Aspect.All(typeof(DummyComponent))) { }
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ // Do NOT initialize mapper in order to test: https://github.com/craftworkgames/MonoGame.Extended/issues/707
+ }
+
+ public void Draw(GameTime gameTime) { }
+
+ public void Update(GameTime gameTime) { }
+
+ protected override void OnEntityAdded(int entityId)
+ {
+ base.OnEntityAdded(entityId);
+ AddedEntitiesId.Add(entityId);
+ }
+
+ protected override void OnEntityRemoved(int entityId)
+ {
+ base.OnEntityRemoved(entityId);
+ RemovedEntitiesId.Add(entityId);
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs
new file mode 100644
index 0000000..7defe1c
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiButtonTests.cs
@@ -0,0 +1,122 @@
+//using System.Collections.Generic;
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.BitmapFonts;
+//using MonoGame.Extended.Gui.Controls;
+//using MonoGame.Extended.Tests;
+//using MonoGame.Extended.TextureAtlases;
+//using NSubstitute;
+//using Xunit;
+
+//namespace MonoGame.Extended.Gui.Tests.Controls
+//{
+// public class GuiButtonTests
+// {
+// [Fact]
+// public void DesiredSizeShouldBeEmptyByDefault()
+// {
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+// var button = new Button();
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize, Is.EqualTo(Size2.Empty));
+// }
+
+// [Fact]
+// public void DesiredSizeShouldBeTheSizeOfTheBackgroundRegion()
+// {
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+// var backgroundRegion = MockTextureRegion();
+// var button = new GuiButton { BackgroundRegion = backgroundRegion };
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize, Is.EqualTo(backgroundRegion.Size));
+// }
+
+// [Fact]
+// public void DesiredSizeShouldBeTheSizeOfTheMarginsInANinePatchRegion()
+// {
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+// var texture = new Texture2D(new TestGraphicsDevice(), 512, 512);
+// var backgroundRegion = new NinePatchRegion2D(new TextureRegion2D(texture), new Thickness(10, 20));
+// var button = new GuiButton() { BackgroundRegion = backgroundRegion };
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize, Is.EqualTo(new Size2(20, 40)));
+// }
+
+// [Fact]
+// public void DesiredSizeShouldAtLeastBeTheSizeOfTheText()
+// {
+// const string text = "abcdefg";
+
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+// var font = CreateMockFont(text, lineHeight: 32);
+// var expectedSize = font.MeasureString(text);
+// var button = new GuiButton {Text = text, Font = font};
+
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize, Is.EqualTo(expectedSize));
+// }
+
+// [Fact]
+// public void DesiredSizeShouldAtLeastBeTheSizeOfTheIcon()
+// {
+// var texture = new Texture2D(new TestGraphicsDevice(), 35, 38);
+// var icon = new TextureRegion2D(texture);
+
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+// var button = new GuiButton { IconRegion = icon };
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize, Is.EqualTo(icon.Size));
+// }
+
+// [Fact]
+// public void DesiredSizeShouldBeTheSizeOfTheBiggestTextOrIcon()
+// {
+// const string text = "abcdefg";
+
+// var texture = new Texture2D(new TestGraphicsDevice(), 35, 38);
+// var icon = new TextureRegion2D(texture);
+// var iconExpectedSize = icon.Size;
+
+// var availableSize = new Size2(800, 480);
+// var context = Substitute.For<IGuiContext>();
+
+// var font = CreateMockFont(text, 32);
+// var fontExpectedSize = font.MeasureString(text);
+
+// var button = new GuiButton { Text = text, Font = font, IconRegion = icon };
+// var desiredSize = button.GetDesiredSize(context, availableSize);
+
+// Assert.That(desiredSize.Width, Is.EqualTo(fontExpectedSize.Width));
+// Assert.That(desiredSize.Height, Is.EqualTo(iconExpectedSize.Height));
+// }
+
+// private static BitmapFont CreateMockFont(string text, int lineHeight)
+// {
+// var regions = new List<BitmapFontRegion>();
+// var xOffset = 0;
+
+// foreach (var character in text)
+// {
+// regions.Add(new BitmapFontRegion(MockTextureRegion(10, 10), character, xOffset, yOffset: 0, xAdvance: 0));
+// xOffset += 10;
+// }
+
+// return new BitmapFont("font", regions, lineHeight);
+// }
+
+// private static TextureRegion2D MockTextureRegion(int width = 100, int height = 200)
+// {
+// var texture = new Texture2D(new TestGraphicsDevice(), width, height);
+// return new TextureRegion2D(texture, 0, 0, width, height);
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiControlCollectionTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiControlCollectionTests.cs
new file mode 100644
index 0000000..a2145b8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/Controls/GuiControlCollectionTests.cs
@@ -0,0 +1,58 @@
+//using MonoGame.Extended.Gui.Controls;
+//using NSubstitute;
+//using Xunit;
+
+//namespace MonoGame.Extended.Gui.Tests.Controls
+//{
+
+// public class GuiControlCollectionTests
+// {
+// [Fact]
+// public void GuiControlCollection_Add_SetsTheParent_Test()
+// {
+// var parent = Substitute.For<GuiControl>();
+// var child = Substitute.For<GuiControl>();
+
+// var controls = new GuiControlCollection(parent) { child };
+// Assert.IsTrue(controls.Contains(child));
+// Assert.AreSame(parent, child.Parent);
+// }
+
+// [Fact]
+// public void GuiControlCollection_Remove_SetsTheParentToNull_Test()
+// {
+// var parent = Substitute.For<GuiControl>();
+// var child = Substitute.For<GuiControl>();
+
+// new GuiControlCollection(parent) { child }.Remove(child);
+
+// Assert.IsNull(child.Parent);
+// }
+
+// [Fact]
+// public void GuiControlCollection_Insert_SetsTheParent_Test()
+// {
+// var parent = Substitute.For<GuiControl>();
+// var child = Substitute.For<GuiControl>();
+
+// var controls = new GuiControlCollection(parent);
+
+// controls.Insert(0, child);
+// Assert.IsTrue(controls.Contains(child));
+// Assert.AreSame(parent, child.Parent);
+// }
+
+// [Fact]
+// public void GuiControlCollection_Clear_SetsAllTheParentsToNull_Test()
+// {
+// var parent = Substitute.For<GuiControl>();
+// var child0 = Substitute.For<GuiControl>();
+// var child1 = Substitute.For<GuiControl>();
+
+// new GuiControlCollection(parent) { child0, child1 }.Clear();
+
+// Assert.IsNull(child0.Parent);
+// Assert.IsNull(child1.Parent);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/GuiRendererTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/GuiRendererTests.cs
new file mode 100644
index 0000000..44b3bc3
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/GuiRendererTests.cs
@@ -0,0 +1,34 @@
+using MonoGame.Extended.Gui.Controls;
+using Xunit;
+
+namespace MonoGame.Extended.Gui.Tests
+{
+ //
+ //public class GuiRendererTests
+ //{
+ // [Fact]
+ // public void GuiRenderer_TargetScreen_Test()
+ // {
+ // var screen = new GuiScreen();
+ // var renderer = new GuiTestRenderer(screen);
+
+ // Assert.That(renderer.TargetScreen, Is.SameAs(screen));
+ // }
+ //}
+
+ //public class GuiTestRenderer : GuiRenderer<TestDrawer>
+ //{
+ // public GuiTestRenderer(GuiScreen targetScreen)
+ // : base(targetScreen)
+ // {
+ // }
+
+ // protected override void DrawControl(TestDrawer drawer, GuiControl control)
+ // {
+ // }
+ //}
+
+ //public class TestDrawer
+ //{
+ //}
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj
new file mode 100644
index 0000000..6e96c86
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Gui\MonoGame.Extended.Gui.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AngleTest.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AngleTest.cs
new file mode 100644
index 0000000..2f2762b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AngleTest.cs
@@ -0,0 +1,92 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests
+{
+ public class AngleTest
+ {
+ private const float _delta = 0.00001f;
+ private readonly WithinDeltaEqualityComparer _withinDeltaEqualityComparer = new(_delta);
+
+ [Fact]
+ public void ConstructorTest()
+ {
+ const float value = 0.5f;
+
+ // ReSharper disable once RedundantArgumentDefaultValue
+ var radians = new Angle(value, AngleType.Radian);
+ var degrees = new Angle(value, AngleType.Degree);
+ var gradians = new Angle(value, AngleType.Gradian);
+ var revolutions = new Angle(value, AngleType.Revolution);
+
+ Assert.Equal(0.5f, radians.Radians, _withinDeltaEqualityComparer);
+ Assert.Equal(0.5f, degrees.Degrees, _withinDeltaEqualityComparer);
+ Assert.Equal(0.5f, gradians.Gradians, _withinDeltaEqualityComparer);
+ Assert.Equal(0.5f, revolutions.Revolutions, _withinDeltaEqualityComparer);
+ }
+
+ [Fact]
+ public void ConversionTest()
+ {
+ //from radians
+ var radians = new Angle(MathHelper.Pi);
+ Assert.Equal(180f, radians.Degrees, _withinDeltaEqualityComparer);
+ Assert.Equal(200f, radians.Gradians, _withinDeltaEqualityComparer);
+ Assert.Equal(0.5f, radians.Revolutions, _withinDeltaEqualityComparer);
+
+ //to radians
+ var degrees = new Angle(180f, AngleType.Degree);
+ var gradians = new Angle(200f, AngleType.Gradian);
+ var revolutions = new Angle(0.5f, AngleType.Revolution);
+
+ Assert.Equal(MathHelper.Pi, degrees.Radians, _withinDeltaEqualityComparer);
+ Assert.Equal(MathHelper.Pi, gradians.Radians, _withinDeltaEqualityComparer);
+ Assert.Equal(MathHelper.Pi, revolutions.Radians, _withinDeltaEqualityComparer);
+ }
+
+ [Fact]
+ public void WrapTest()
+ {
+ for (var f = -10f; f < 10f; f += 0.1f)
+ {
+ var wrappositive = new Angle(f);
+ wrappositive.WrapPositive();
+
+ var wrap = new Angle(f);
+ wrap.Wrap();
+
+ Assert.True(wrappositive.Radians >= 0);
+ Assert.True(wrappositive.Radians < 2d * MathHelper.Pi);
+
+ Assert.True(wrap.Radians >= -MathHelper.Pi);
+ Assert.True(wrap.Radians < MathHelper.Pi);
+ }
+ }
+
+ [Fact]
+ public void VectorTest()
+ {
+ var angle = Angle.FromVector(Vector2.One);
+ Assert.Equal(-MathHelper.Pi / 4f, angle.Radians, _withinDeltaEqualityComparer);
+ Assert.Equal(10f, angle.ToVector(10f).Length());
+
+ angle = Angle.FromVector(Vector2.UnitX);
+ Assert.Equal(0, angle.Radians, _withinDeltaEqualityComparer);
+ Assert.True(Vector2.UnitX.EqualsWithTolerence(angle.ToUnitVector()));
+
+ angle = Angle.FromVector(-Vector2.UnitY);
+ Assert.Equal(MathHelper.Pi / 2f, angle.Radians, _withinDeltaEqualityComparer);
+ Assert.True((-Vector2.UnitY).EqualsWithTolerence(angle.ToUnitVector()));
+ }
+
+ [Fact]
+ public void EqualsTest()
+ {
+ var angle1 = new Angle(0);
+ var angle2 = new Angle(MathHelper.Pi * 2f);
+ Assert.True(angle1 == angle2);
+ angle2.Radians = MathHelper.Pi * 4f;
+ Assert.True(angle1.Equals(angle2));
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AssertExtensions.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AssertExtensions.cs
new file mode 100644
index 0000000..72347ca
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/AssertExtensions.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace MonoGame.Extended.Tests
+{
+ public static class AssertExtensions
+ {
+ public static bool AreApproximatelyEqual(Point2 firstPoint, Point2 secondPoint)
+ {
+ return Math.Abs(firstPoint.X - secondPoint.X) < float.Epsilon &&
+ Math.Abs(firstPoint.Y - secondPoint.Y) < float.Epsilon;
+ }
+
+ public static bool AreApproximatelyEqual(RectangleF firstRectangle, RectangleF secondRectangle)
+ {
+ return Math.Abs(firstRectangle.X - secondRectangle.X) < float.Epsilon &&
+ Math.Abs(firstRectangle.Y - secondRectangle.Y) < float.Epsilon &&
+ Math.Abs(firstRectangle.Width - secondRectangle.Width) < float.Epsilon &&
+ Math.Abs(firstRectangle.Height - secondRectangle.Height) < float.Epsilon;
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/BitmapFonts/BitmapFontTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/BitmapFonts/BitmapFontTests.cs
new file mode 100644
index 0000000..8f751bc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/BitmapFonts/BitmapFontTests.cs
@@ -0,0 +1,79 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.BitmapFonts;
+using MonoGame.Extended.TextureAtlases;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.BitmapFonts
+{
+ public class BitmapFontTests
+ {
+ [Fact]
+ public void BitmapFont_Constructor_Test()
+ {
+ var font = CreateTestFont();
+
+ Assert.Equal("Impact", font.Name);
+ Assert.Equal(22, font.LineHeight);
+ }
+
+ [Fact]
+ public void BitmapFont_MeasureString_SingleWord_Test()
+ {
+ var font = CreateTestFont();
+ var size = font.MeasureString("fox");
+
+ Assert.Equal(40, size.Width);
+ Assert.Equal(font.LineHeight, size.Height);
+ }
+
+ [Fact]
+ public void BitmapFont_MeasureString_WithLetterSpacing_Test()
+ {
+ var font = CreateTestFont();
+ font.LetterSpacing = 3;
+
+ var size = font.MeasureString("fox");
+
+ Assert.Equal(46, size.Width);
+ Assert.Equal(size.Height, font.LineHeight);
+ }
+
+ [Fact]
+ public void BitmapFont_MeasureString_MultipleLines_Test()
+ {
+ var font = CreateTestFont();
+ var size = font.MeasureString("box fox\nbox of fox");
+
+ Assert.Equal(123, size.Width);
+ Assert.Equal(size.Height, font.LineHeight * 2);
+ }
+
+ [Fact]
+ public void BitmapFont_MeasureString_EmptyString_Test()
+ {
+ var font = CreateTestFont();
+ var size = font.MeasureString(string.Empty);
+
+ Assert.Equal(0, size.Width);
+ Assert.Equal(0, size.Height);
+ }
+
+ private static BitmapFont CreateTestFont()
+ {
+ var textureRegion = new TextureRegion2D(null, x: 219, y: 61, width: 16, height: 18);
+ var regions = new[]
+ {
+ // extracted from 'Impact' font. 'x' is particularly interesting because it has a negative x offset
+ new BitmapFontRegion(textureRegion, character: ' ', xOffset: 0, yOffset: 0, xAdvance: 6),
+ new BitmapFontRegion(textureRegion, character: 'b', xOffset: 0, yOffset: 7, xAdvance: 17),
+ new BitmapFontRegion(textureRegion, character: 'f', xOffset: 0, yOffset: 7, xAdvance: 9),
+ new BitmapFontRegion(textureRegion, character: 'o', xOffset: 0, yOffset: 11, xAdvance: 16),
+ new BitmapFontRegion(textureRegion, character: 'x', xOffset: -1, yOffset: 11, xAdvance: 13),
+ };
+
+ var font = new BitmapFont("Impact", regions, lineHeight: 22);
+ return font;
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Camera2DTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Camera2DTests.cs
new file mode 100644
index 0000000..2f188b8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Camera2DTests.cs
@@ -0,0 +1,97 @@
+//using Microsoft.Xna.Framework;
+//using MonoGame.Extended.ViewportAdapters;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests
+//{
+//
+// public class Camera2DTests
+// {
+// [Fact]
+// public void Camera2D_LookAt_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var viewportAdapter = new DefaultViewportAdapter(graphicsDevice);
+// var camera = new OrthographicCamera(viewportAdapter);
+
+// camera.LookAt(new Vector2(100, 200));
+
+// Assert.Equal(new Vector2(-300, -40), camera.Position);
+// }
+
+// [Fact]
+// public void Camera2D_GetBoundingFrustum_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var camera = new OrthographicCamera(graphicsDevice);
+// var boundingFrustum = camera.GetBoundingFrustum();
+// var corners = boundingFrustum.GetCorners();
+
+// const float delta = 0.01f;
+// TestHelper.AreEqual(new Vector3(0, 0, 1), corners[0], delta);
+// TestHelper.AreEqual(new Vector3(800, 0, 1), corners[1], delta);
+// TestHelper.AreEqual(new Vector3(800, 480, 1), corners[2], delta);
+// TestHelper.AreEqual(new Vector3(0, 480, 1), corners[3], delta);
+// TestHelper.AreEqual(new Vector3(0, 0, 0), corners[4], delta);
+// TestHelper.AreEqual(new Vector3(800, 0, 0), corners[5], delta);
+// TestHelper.AreEqual(new Vector3(800, 480, 0), corners[6], delta);
+// TestHelper.AreEqual(new Vector3(0, 480, 0), corners[7], delta);
+// }
+
+// [Fact]
+// public void Camera2D_BoundingRectangle_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var viewport = new DefaultViewportAdapter(graphicsDevice);
+// var camera = new OrthographicCamera(viewport);
+// camera.Move(new Vector2(2, 0));
+// camera.Move(new Vector2(0, 3));
+
+// var boundingRectangle = camera.BoundingRectangle;
+
+// Assert.Equal(2, boundingRectangle.Left, 0.01);
+// Assert.Equal(3, boundingRectangle.Top, 0.01);
+// Assert.Equal(802, boundingRectangle.Right, 0.01);
+// Assert.Equal(483, boundingRectangle.Bottom, 0.01);
+// }
+
+// [Fact]
+// public void Camera2D_ContainsPoint_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var camera = new OrthographicCamera(graphicsDevice);
+
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Point(1, 1)));
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Point(799, 479)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Point(-1, -1)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Point(801, 481)));
+// }
+
+// [Fact]
+// public void Camera2D_ContainsVector2_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var camera = new OrthographicCamera(graphicsDevice);
+
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(799.5f, 479.5f)));
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(0.5f, 0.5f)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(-0.5f, -0.5f)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(800.5f, 480.5f)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(-0.5f, 240f)));
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(0.5f, 240f)));
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Vector2(799.5f, 240f)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Vector2(800.5f, 240f)));
+// }
+
+// [Fact]
+// public void Camera2D_ContainsRectangle_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var camera = new OrthographicCamera(graphicsDevice);
+
+// Assert.Equal(ContainmentType.Intersects, camera.Contains(new Rectangle(-50, -50, 100, 100)));
+// Assert.Equal(ContainmentType.Contains, camera.Contains(new Rectangle(50, 50, 100, 100)));
+// Assert.Equal(ContainmentType.Disjoint, camera.Contains(new Rectangle(850, 500, 100, 100)));
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/CollectionAssert.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/CollectionAssert.cs
new file mode 100644
index 0000000..15c105b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/CollectionAssert.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Xunit;
+
+namespace MonoGame.Extended.Tests;
+
+public static class CollectionAssert
+{
+ public static void Equal<T>(IReadOnlyList<T> expected, IReadOnlyList<T> actual)
+ {
+ Assert.True(expected.Count == actual.Count, "The number of items in the collections does not match.");
+
+ Assert.All(actual, x => Assert.Contains(x, expected));
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/BagTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/BagTests.cs
new file mode 100644
index 0000000..f89b45b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/BagTests.cs
@@ -0,0 +1,48 @@
+using MonoGame.Extended.Collections;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Collections
+{
+ public class BagTests
+ {
+ [Fact]
+ public void Bag_Enumeration_Does_Not_Allocate()
+ {
+ var bag = new Bag<int>();
+ for (int i = 0; i < 100; i++) bag.Add(i);
+ // ensure we have plenty of memory and that the heap only increases for the duration of this test
+ Assert.True(GC.TryStartNoGCRegion(Unsafe.SizeOf<Bag<int>.BagEnumerator>() * 1000));
+ var heapSize = GC.GetAllocatedBytesForCurrentThread();
+
+ // this should NOT allocate
+ foreach (int i in bag)
+ {
+ // assert methods cause the NoGCRegion to fail, so do this manually
+ if (GC.GetAllocatedBytesForCurrentThread() != heapSize)
+ Assert.True(false);
+ }
+
+ // sanity check: this SHOULD allocate
+ foreach (int _ in (IEnumerable<int>)bag)
+ {
+ // assert methods cause the NoGCRegion to fail, so do this manually
+ if (GC.GetAllocatedBytesForCurrentThread() == heapSize)
+ Assert.True(false);
+ }
+
+ // Wrap in if statement due to exception thrown when running test through
+ // cake build script or when debugging test script manually.
+ if(GCSettings.LatencyMode == GCLatencyMode.NoGCRegion)
+ {
+ GC.EndNoGCRegion();
+ }
+ }
+
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/DequeTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/DequeTests.cs
new file mode 100644
index 0000000..3196309
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Collections/DequeTests.cs
@@ -0,0 +1,408 @@
+using System;
+using System.Linq;
+using MonoGame.Extended.Collections;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Collections
+{
+ public class DequeTests
+ {
+ private class TestDequeElement
+ {
+ public int Value { get; set; }
+ }
+
+ private readonly Random _random;
+
+ public DequeTests()
+ {
+ _random = new Random();
+ }
+
+ [Fact]
+ public void Deque_Constructor_Default()
+ {
+ var deque = new Deque<object>();
+ Assert.True(deque.Count == 0);
+ Assert.True(deque.Capacity == 0);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Constructor_Collection(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ Assert.True(deque.Count == count);
+ Assert.True(deque.Capacity == count);
+ for (var index = 0; index < deque.Count; index++)
+ {
+ Assert.True(deque[index].Value == index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Constructor_Capacity(int capacity)
+ {
+ var deque = new Deque<TestDequeElement>(capacity);
+ Assert.True(deque.Count == 0);
+ Assert.True(deque.Capacity == capacity);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Clear(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ deque.Clear();
+ Assert.True(deque.Count == 0);
+ Assert.True(deque.Capacity >= count);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Trim_And_Clear(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ deque.Clear();
+ deque.TrimExcess();
+ Assert.True(deque.Count == 0);
+ Assert.True(deque.Capacity == 0);
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Trim_Front(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+
+ for (var i = 0; i < count; i++)
+ {
+ deque.RemoveFromFront(out _);
+ deque.Capacity = deque.Count;
+ Assert.True(deque.Count == count - 1 - i);
+ Assert.True(deque.Capacity == count - 1 - i);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Trim_Back(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+
+ for (var i = 0; i < count; i++)
+ {
+ deque.RemoveFromBack(out _);
+ deque.Capacity = deque.Count;
+ Assert.True(deque.Count == count - 1 - i);
+ Assert.True(deque.Capacity == count - 1 - i);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Add_Front(int count)
+ {
+ var deque = new Deque<TestDequeElement>();
+ for (var i = 0; i < count; i++)
+ {
+ deque.AddToFront(new TestDequeElement
+ {
+ Value = i
+ });
+ }
+ Assert.True(deque.Count == count);
+ Assert.True(deque.Capacity >= count);
+ for (var index = 0; index < deque.Count; index++)
+ {
+ var element = deque[index];
+ Assert.True(element.Value == deque.Count - 1 - index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Add_Back(int count)
+ {
+ var deque = new Deque<TestDequeElement>();
+ for (var i = 0; i < count; i++)
+ {
+ deque.AddToBack(new TestDequeElement
+ {
+ Value = i
+ });
+ }
+ Assert.True(deque.Count == count);
+ Assert.True(deque.Capacity >= count);
+ for (var index = 0; index < deque.Count; index++)
+ {
+ var element = deque[index];
+ Assert.True(element.Value == index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Remove_Front(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+
+ var index = 0;
+ while (deque.RemoveFromFront(out var element))
+ {
+ Assert.True(element.Value == index);
+ index++;
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Remove_Back(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+
+ var index = 0;
+ while (deque.RemoveFromBack(out var element))
+ {
+ Assert.True(element.Value == elements.Length - 1 - index);
+ index++;
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Get_Front(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var indices = Enumerable.Range(0, count);
+ foreach (var index in indices)
+ {
+ deque.GetFront(out var element);
+ deque.RemoveFromFront();
+ Assert.True(element.Value == index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Get_Back(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var indices = Enumerable.Range(0, count);
+ foreach (var index in indices)
+ {
+ deque.GetBack(out var element);
+ deque.RemoveFromBack();
+ Assert.True(element.Value == count - 1 - index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Get_Index(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var indices = Enumerable.Range(0, count).ToList().Shuffle(_random);
+ foreach (var index in indices)
+ {
+ deque.Get(index, out var element);
+ Assert.True(element.Value == index);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_ForEach_Iteration(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var counter = 0;
+ foreach (var element in deque)
+ {
+ Assert.True(element.Value == counter);
+ counter++;
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_ForEach_Iteration_Modified(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var counter = 0;
+ foreach (var element in deque)
+ {
+ Assert.True(element.Value == counter);
+ counter++;
+ deque.RemoveFromFront();
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(50)]
+ public void Deque_Remove(int count)
+ {
+ var elements = new TestDequeElement[count];
+ for (var i = 0; i < count; i++)
+ {
+ elements[i] = new TestDequeElement
+ {
+ Value = i
+ };
+ }
+ var deque = new Deque<TestDequeElement>(elements);
+ var counter = count;
+ while (deque.Count > 0)
+ {
+ var index = _random.Next(0, deque.Count - 1);
+ deque.RemoveAt(index);
+ counter--;
+ Assert.True(deque.Count == counter);
+ }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Content/ContentReaderExtensionsTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Content/ContentReaderExtensionsTests.cs
new file mode 100644
index 0000000..e1c701d
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Content/ContentReaderExtensionsTests.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Linq;
+using MonoGame.Extended.Collections;
+using MonoGame.Extended.Content;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Content
+{
+ public class ContentReaderExtensionsTests
+ {
+ [Theory]
+ [InlineData("testsuperdir/testsubdir1/testsubdir2/resource", "testsuperdir/testsubdir1/testsubdir2/resource")]
+ [InlineData("testsuperdir/testsubdir1/../testsubdir2/resource","testsuperdir/testsubdir2/resource")]
+ [InlineData("testsuperdir/../resource","resource")]
+ [InlineData("../testsuperdir/testsubdir1/../testsubdir2/resource","../testsuperdir/testsubdir2/resource")]
+ [InlineData("testsuperdir/testsubdir1/testsubdir2/../../testsubdir3/resource","testsuperdir/testsubdir3/resource")]
+ [InlineData("testsuperdir/testsubdir1/../testsubdir2/../testsubdir3/resource", "testsuperdir/testsubdir3/resource")]
+ public void ContentReaderExtensions_ShortenRelativePath(string input, string expectedoutput)
+ {
+ Assert.True(ContentReaderExtensions.ShortenRelativePath(input) == expectedoutput);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MockGameWindow.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MockGameWindow.cs
new file mode 100644
index 0000000..a65479b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MockGameWindow.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Drawing;
+using Microsoft.Xna.Framework;
+using Point = Microsoft.Xna.Framework.Point;
+using Rectangle = Microsoft.Xna.Framework.Rectangle;
+
+namespace MonoGame.Extended.Tests
+{
+ public class MockGameWindow : GameWindow
+ {
+ public override bool AllowUserResizing { get; set; }
+ public override Rectangle ClientBounds { get; }
+ public override Point Position { get; set; }
+ public override DisplayOrientation CurrentOrientation { get; }
+ public override IntPtr Handle { get; }
+ public override string ScreenDeviceName { get; }
+
+ public MockGameWindow()
+ {
+ }
+
+ public override void BeginScreenDeviceChange(bool willBeFullScreen)
+ {
+ }
+
+ public override void EndScreenDeviceChange(string screenDeviceName, int clientWidth, int clientHeight)
+ {
+ }
+
+ protected override void SetSupportedOrientations(DisplayOrientation orientations)
+ {
+ }
+
+ protected override void SetTitle(string title)
+ {
+ }
+
+#if __MonoCS__
+ public override Icon Icon { get; set; }
+#endif
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj
new file mode 100644
index 0000000..9e831ff
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended\MonoGame.Extended.csproj" />
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Particles\MonoGame.Extended.Particles.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Folder Include="Content\" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/OpenTK.dll.config b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/OpenTK.dll.config
new file mode 100644
index 0000000..1dc6f45
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/OpenTK.dll.config
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <dllmap os="linux" dll="opengl32.dll" target="libGL.so.1" />
+ <dllmap os="linux" dll="glu32.dll" target="libGLU.so.1" />
+ <dllmap os="linux" dll="openal32.dll" target="libopenal.so.1" />
+ <dllmap os="linux" dll="alut.dll" target="libalut.so.0" />
+ <dllmap os="linux" dll="opencl.dll" target="libOpenCL.so" />
+ <dllmap os="linux" dll="libX11" target="libX11.so.6" />
+ <dllmap os="linux" dll="libXi" target="libXi.so.6" />
+ <dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0" />
+ <dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL" />
+ <dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+ <dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
+ <dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+ <dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+ <dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
+ <dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL" />
+ <dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib" />
+ <!-- XQuartz compatibility (X11 on Mac) -->
+ <dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib" />
+ <dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib" />
+ <dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib" />
+ <dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib" />
+ <dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib" />
+ <dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib" />
+</configuration>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs
new file mode 100644
index 0000000..ff23d8b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/AssertionModifier.cs
@@ -0,0 +1,25 @@
+//using System;
+//using MonoGame.Extended.Particles;
+//using MonoGame.Extended.Particles.Modifiers;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Particles
+//{
+// internal class AssertionModifier : Modifier
+// {
+// private readonly Predicate<Particle> _predicate;
+
+// public AssertionModifier(Predicate<Particle> predicate)
+// {
+// _predicate = predicate;
+// }
+
+// public override unsafe void Update(float elapsedSeconds, ParticleBuffer.ParticleIterator iterator)
+// {
+// while (iterator.HasNext) {
+// var particle = iterator.Next();
+// Assert.IsTrue(_predicate(*particle));
+// }
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ColourTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ColourTests.cs
new file mode 100644
index 0000000..49b5e03
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ColourTests.cs
@@ -0,0 +1,126 @@
+using System;
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Particles
+{
+ public class ColourTests
+ {
+ public class Constructor
+ {
+ [Fact]
+ public void WhenGivenValues_ReturnsInitializedColour()
+ {
+ var colour = new HslColor(1f, 1f, 1f);
+ Assert.Equal(1f, colour.H);
+ Assert.Equal(1f, colour.S);
+ Assert.Equal(1f, colour.L);
+ }
+ }
+
+ public class AreEqualColourMethod
+ {
+ [Fact]
+ public void WhenGivenEqualValues_ReturnsTrue()
+ {
+ var x = new HslColor(360f, 1f, 1f);
+ var y = new HslColor(360f, 1f, 1f);
+ Assert.Equal(x, y);
+ }
+
+ [Fact]
+ public void WhenGivenDifferentValues_ReturnsFalse()
+ {
+ var x = new HslColor(0f, 1f, 0f);
+ var y = new HslColor(360f, 1f, 1f);
+ Assert.False(x.Equals(y));
+ }
+ }
+
+ public class AreEqualObjectMethod
+ {
+ [Fact]
+ public void WhenGivenEqualColour_ReturnsTrue()
+ {
+ var x = new HslColor(360f, 1f, 1f);
+
+ Object y = new HslColor(360f, 1f, 1f);
+ Assert.Equal(x, y);
+ }
+
+ [Fact]
+ public void WhenGivenDifferentColour_ReturnsFalse()
+ {
+ var x = new HslColor(360f, 1f, 1f);
+
+ Object y = new HslColor(0f, 1f, 0f);
+ Assert.False(x.Equals(y));
+ }
+
+ [Fact]
+ public void WhenGivenObjectOfAntotherType_ReturnsFalse()
+ {
+ var colour = new HslColor(360f, 1f, 1f);
+
+ // ReSharper disable once SuspiciousTypeConversion.Global
+ Assert.False(colour.Equals(DateTime.Now));
+ }
+ }
+
+ public class GetHashCodeMethod
+ {
+ [Fact]
+ public void WhenObjectsAreDifferent_YieldsDifferentHashCodes()
+ {
+ var x = new HslColor(0f, 1f, 0f);
+ var y = new HslColor(360f, 1f, 1f);
+ Assert.NotEqual(x.GetHashCode(), y.GetHashCode());
+ }
+
+ [Fact]
+ public void WhenObjectsAreSame_YieldsIdenticalHashCodes()
+ {
+ var x = new HslColor(180f, 0.5f, 0.5f);
+ var y = new HslColor(180f, 0.5f, 0.5f);
+ Assert.Equal(x.GetHashCode(), y.GetHashCode());
+ }
+ }
+
+ public class ToStringMethod
+ {
+ [Theory]
+ [InlineData(360f, 1f, 1f, "H:0.0° S:100.0 L:100.0")]
+ [InlineData(180f, 0.5f, 0.5f, "H:180.0° S:50.0 L:50.0")]
+ [InlineData(0f, 0f, 0f, "H:0.0° S:0.0 L:0.0")]
+ public void ReturnsCorrectValue(float h, float s, float l, string expected)
+ {
+ var colour = new HslColor(h, s, l);
+ Assert.Equal(expected, colour.ToString());
+ }
+ }
+
+ public class ToRgbMethod
+ {
+ [Theory]
+ [InlineData(0f, 1f, 0.5f, "{R:255 G:0 B:0 A:255}")] // Color.Red
+ [InlineData(360f, 1f, 0.5f, "{R:255 G:0 B:0 A:255}")] // Color.Red
+ [InlineData(120f, 1f, 0.5f, "{R:0 G:255 B:0 A:255}")] // Color.Lime
+ public void ReturnsCorrectValue(float h, float s, float l, string expected)
+ {
+ var hslColour = new HslColor(h, s, l);
+ Color rgbColor = hslColour.ToRgb();
+
+ Assert.Equal(expected, rgbColor.ToString());
+ }
+
+ [Fact]
+ public void FromRgbAndToRgbWorksCorrectly()
+ {
+ HslColor blueHsl = HslColor.FromRgb(Color.Blue);
+ Color blueRgb = blueHsl.ToRgb();
+
+ Assert.Equal(Color.Blue, blueRgb);
+ }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs
new file mode 100644
index 0000000..4ec9b65
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/EmitterTests.cs
@@ -0,0 +1,131 @@
+//using System;
+//using Microsoft.Xna.Framework;
+//using MonoGame.Extended.Particles;
+//using MonoGame.Extended.Particles.Modifiers;
+//using MonoGame.Extended.Particles.Profiles;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Particles
+//{
+//
+// public class EmitterTests
+// {
+// public class UpdateMethod
+// {
+// [Fact]
+// public void WhenThereAreParticlesToExpire_DecreasesActiveParticleCount()
+// {
+// var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point())
+// {
+// AutoTrigger = false,
+// Parameters = new ParticleReleaseParameters
+// {
+// Quantity = 1
+// }
+// };
+
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 1);
+
+// subject.Update(2f);
+// Assert.Equal(subject.ActiveParticles, 0);
+// }
+
+// [Fact]
+// public void WhenThereAreParticlesToExpire_DoesNotPassExpiredParticlesToModifiers()
+// {
+// var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point())
+// {
+// Parameters = new ParticleReleaseParameters()
+// {
+// Quantity = 1
+// },
+// Modifiers =
+// {
+// new AssertionModifier(particle => particle.Age <= 1f)
+// }
+// };
+
+// subject.Trigger(new Vector2(0f, 0f));
+// subject.Update(0.5f);
+// subject.Trigger(new Vector2(0f, 0f));
+// subject.Update(0.5f);
+// subject.Trigger(new Vector2(0f, 0f));
+// subject.Update(0.5f);
+// }
+
+// [Fact]
+// public void WhenThereAreNoActiveParticles_GracefullyDoesNothing()
+// {
+// var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point()) { AutoTrigger = false };
+
+// subject.Update(0.5f);
+// Assert.Equal(subject.ActiveParticles, 0);
+// }
+// }
+
+// public class TriggerMethod
+// {
+// [Fact]
+// public void WhenEnoughHeadroom_IncreasesActiveParticlesCountByReleaseQuantity()
+// {
+// var subject = new ParticleEmitter(null, 100, TimeSpan.FromSeconds(1), Profile.Point())
+// {
+// Parameters = new ParticleReleaseParameters
+// {
+// Quantity = 10
+// }
+// };
+// Assert.Equal(subject.ActiveParticles, 0);
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 10);
+// }
+
+// [Fact]
+// public void WhenNotEnoughHeadroom_IncreasesActiveParticlesCountByRemainingParticles()
+// {
+// var subject = new ParticleEmitter(null, 15, TimeSpan.FromSeconds(1), Profile.Point())
+// {
+// Parameters = new ParticleReleaseParameters
+// {
+// Quantity = 10
+// }
+// };
+
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 10);
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 15);
+// }
+
+// [Fact]
+// public void WhenNoRemainingParticles_DoesNotIncreaseActiveParticlesCount()
+// {
+// var subject = new ParticleEmitter(null, 10, TimeSpan.FromSeconds(1), Profile.Point())
+// {
+// Parameters = new ParticleReleaseParameters
+// {
+// Quantity = 10
+// }
+// };
+
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 10);
+// subject.Trigger(new Vector2(0f, 0f));
+// Assert.Equal(subject.ActiveParticles, 10);
+// }
+// }
+
+// public class DisposeMethod
+// {
+// [Fact]
+// public void IsIdempotent()
+// {
+// var subject = new ParticleEmitter(null, 10, TimeSpan.FromSeconds(1), Profile.Point());
+
+// subject.Dispose();
+// subject.Dispose();
+// }
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ParticleBufferTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ParticleBufferTests.cs
new file mode 100644
index 0000000..ef2e159
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/ParticleBufferTests.cs
@@ -0,0 +1,184 @@
+//using System;
+//using MonoGame.Extended.Particles;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Particles
+//{
+//
+// public class ParticleBufferTests
+// {
+// public class AvailableProperty
+// {
+// [Fact]
+// public void WhenNoParticlesReleased_ReturnsBufferSize()
+// {
+// var subject = new ParticleBuffer(100);
+
+// Assert.Equal(subject.Available, 100);
+// }
+
+// [Fact]
+// public void WhenSomeParticlesReleased_ReturnsAvailableCount()
+// {
+// var subject = new ParticleBuffer(100);
+
+// subject.Release(10);
+// Assert.Equal(subject.Available, 90);
+// }
+
+// [Fact]
+// public void WhenAllParticlesReleased_ReturnsZero()
+// {
+// var subject = new ParticleBuffer(100);
+
+// subject.Release(100);
+// Assert.Equal(subject.Available, 0);
+
+// }
+// }
+
+// public class CountProperty
+// {
+// [Fact]
+// public void WhenNoParticlesReleased_ReturnsZero()
+// {
+// var subject = new ParticleBuffer(100);
+// Assert.Equal(subject.Count, 0);
+// }
+
+// [Fact]
+// public void WhenSomeParticlesReleased_ReturnsCount()
+// {
+// var subject = new ParticleBuffer(100);
+
+// subject.Release(10);
+// Assert.Equal(subject.Count, 10);
+
+// }
+
+// [Fact]
+// public void WhenAllParticlesReleased_ReturnsZero()
+// {
+// var subject = new ParticleBuffer(100);
+
+// subject.Release(100);
+// Assert.Equal(subject.Count, 100);
+
+// }
+// }
+
+// public class ReleaseMethod
+// {
+// [Fact]
+// public void WhenPassedReasonableQuantity_ReturnsNumberReleased()
+// {
+// var subject = new ParticleBuffer(100);
+
+// var count = subject.Release(50);
+
+// Assert.Equal(count.Total, 50);
+// }
+
+// [Fact]
+// public void WhenPassedImpossibleQuantity_ReturnsNumberActuallyReleased()
+// {
+// var subject = new ParticleBuffer(100);
+
+// var count = subject.Release(200);
+// Assert.Equal(count.Total, 100);
+// }
+// }
+
+// public class ReclaimMethod
+// {
+// [Fact]
+// public void WhenPassedReasonableNumber_ReclaimsParticles()
+// {
+// var subject = new ParticleBuffer(100);
+
+// subject.Release(100);
+// Assert.Equal(subject.Count, 100);
+
+// subject.Reclaim(50);
+// Assert.Equal(subject.Count, 50);
+// }
+// }
+
+// //public class CopyToMethod
+// //{
+// // [Fact]
+// // public void WhenBufferIsSequential_CopiesParticlesInOrder()
+// // {
+// // unsafe
+// // {
+// // var subject = new ParticleBuffer(10);
+// // var iterator = subject.Release(5);
+
+// // do
+// // {
+// // var particle = iterator.Next();
+// // particle->Age = 1f;
+// // }
+// // while (iterator.HasNext);
+
+// // var destination = new Particle[10];
+
+// // fixed (Particle* buffer = destination)
+// // {
+// // subject.CopyTo((IntPtr)buffer);
+// // }
+
+// // Assert.Equal(destination[0].Age, 1f, 0.0001);
+// // Assert.Equal(destination[1].Age, 1f, 0.0001);
+// // Assert.Equal(destination[2].Age, 1f, 0.0001);
+// // Assert.Equal(destination[3].Age, 1f, 0.0001);
+// // Assert.Equal(destination[4].Age, 1f, 0.0001);
+// // }
+// // }
+// //}
+
+// //public class CopyToReverseMethod
+// //{
+// // [Fact]
+// // public void WhenBufferIsSequential_CopiesParticlesInReverseOrder()
+// // {
+// // unsafe
+// // {
+// // var subject = new ParticleBuffer(10);
+// // var iterator = subject.Release(5);
+
+// // do
+// // {
+// // var particle = iterator.Next();
+// // particle->Age = 1f;
+// // }
+// // while (iterator.HasNext);
+
+// // var destination = new Particle[10];
+
+// // fixed (Particle* buffer = destination)
+// // {
+// // subject.CopyToReverse((IntPtr)buffer);
+// // }
+
+// // Assert.Equal(destination[0].Age, 1f, 0.0001);
+// // Assert.Equal(destination[1].Age, 1f, 0.0001);
+// // Assert.Equal(destination[2].Age, 1f, 0.0001);
+// // Assert.Equal(destination[3].Age, 1f, 0.0001);
+// // Assert.Equal(destination[4].Age, 1f, 0.0001);
+// // }
+// // }
+// //}
+
+// public class DisposeMethod
+// {
+// [Fact]
+// public void IsIdempotent()
+// {
+// var subject = new ParticleBuffer(100);
+// subject.Dispose();
+// subject.Dispose();
+// }
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/PointProfileTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/PointProfileTests.cs
new file mode 100644
index 0000000..cb22b3b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/PointProfileTests.cs
@@ -0,0 +1,33 @@
+using System;
+using MonoGame.Extended.Particles.Profiles;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Particles.Profiles
+{
+
+ public class PointProfileTests
+ {
+ [Fact]
+ public void ReturnsZeroOffset()
+ {
+ var subject = new PointProfile();
+
+ subject.GetOffsetAndHeading(out var offset, out _);
+
+ Assert.Equal(0f, offset.X);
+ Assert.Equal(0f, offset.Y);
+ }
+
+ [Fact]
+ public void ReturnsHeadingAsUnitVector()
+ {
+ var subject = new PointProfile();
+
+ subject.GetOffsetAndHeading(out _, out var heading);
+
+ var length = Math.Sqrt(heading.X * heading.X + heading.Y * heading.Y);
+ Assert.Equal(1f, length, 6);
+ }
+
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/RingProfileTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/RingProfileTests.cs
new file mode 100644
index 0000000..a18f5b7
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Particles/Profiles/RingProfileTests.cs
@@ -0,0 +1,38 @@
+using System;
+using MonoGame.Extended.Particles.Profiles;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Particles.Profiles
+{
+ public class RingProfileTests
+ {
+ [Fact]
+ public void ReturnsOffsetEqualToRadius()
+ {
+ var subject = new RingProfile
+ {
+ Radius = 10f
+ };
+ subject.GetOffsetAndHeading(out var offset, out _);
+
+ var length = Math.Sqrt(offset.X * offset.X + offset.Y * offset.Y);
+ Assert.Equal(10f, length, 6);
+ }
+
+ [Fact]
+ public void WhenRadiateIsTrue_HeadingIsEqualToNormalizedOffset()
+ {
+ var subject = new RingProfile
+ {
+ Radius = 10f,
+ Radiate = Profile.CircleRadiation.Out
+ };
+ subject.GetOffsetAndHeading(out var offset, out var heading);
+
+ Assert.Equal(heading.X, offset.X / 10, 6);
+ Assert.Equal(heading.Y, offset.Y / 10, 6);
+
+ }
+
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/BoundingRectangleTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/BoundingRectangleTests.cs
new file mode 100644
index 0000000..6f95cf5
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/BoundingRectangleTests.cs
@@ -0,0 +1,396 @@
+//using System.Collections.Generic;
+//using System.Globalization;
+//using Microsoft.Xna.Framework;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Primitives
+//{
+//
+// public class BoundingRectangleTests
+// {
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Vector2()).SetName(
+// "The empty bounding rectangle has the expected position and radii.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Vector2(15, 15)).SetName(
+// "A non-empty bounding rectangle has the expected position and radii.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(Point2 centre, Vector2 radii)
+// {
+// var boundingRectangle = new BoundingRectangle(centre, radii);
+// Assert.Equal(centre, boundingRectangle.Center);
+// Assert.Equal(radii, boundingRectangle.HalfExtents);
+// }
+
+// public IEnumerable<TestCaseData> CreateFromMinimumMaximumTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), new BoundingRectangle()).SetName(
+// "The bounding rectangle created from the zero minimum point and zero maximum point is the empty bounding rectangle.")
+// ;
+// yield return
+// new TestCaseData(new Point2(5, 5), new Point2(15, 15),
+// new BoundingRectangle(new Point2(10, 10), new Size2(5, 5))).SetName(
+// "The bounding rectangle created from the non-zero minimum point and the non-zero maximum point is the expected bounding rectangle.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CreateFromMinimumMaximumTestCases))]
+// public void CreateFromMinimumMaximum(Point2 minimum, Point2 maximum, BoundingRectangle expectedBoundingRectangle)
+// {
+// var actualBoundingRectangle = BoundingRectangle.CreateFrom(minimum, maximum);
+// Assert.Equal(expectedBoundingRectangle, actualBoundingRectangle);
+// }
+
+// public IEnumerable<TestCaseData> CreateFromPointsTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(null, new BoundingRectangle()).SetName(
+// "The bounding rectangle created from null points is the empty bounding rectangle.");
+// yield return
+// new TestCaseData(new Point2[0], new BoundingRectangle()).SetName(
+// "The bounding rectangle created from the empty set of points is the empty bounding rectangle.");
+// yield return
+// new TestCaseData(
+// new[]
+// {
+// new Point2(5, 5), new Point2(10, 10), new Point2(15, 15), new Point2(-5, -5),
+// new Point2(-15, -15)
+// }, new BoundingRectangle(new Point2(0, 0), new Size2(15, 15))).SetName(
+// "The bounding rectangle created from a non-empty set of points is the expected bounding rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CreateFromPointsTestCases))]
+// public void CreateFromPoints(Point2[] points, BoundingRectangle expectedBoundingRectangle)
+// {
+// var actualBoundingRectangle = BoundingRectangle.CreateFrom(points);
+// Assert.Equal(expectedBoundingRectangle, actualBoundingRectangle);
+// }
+
+// public IEnumerable<TestCaseData> CreateFromTransformedTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), Matrix2.Identity, new BoundingRectangle()).SetName(
+// "The bounding rectangle created from the empty bounding rectangle transformed by the identity matrix is the empty bounding rectangle.")
+// ;
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(20, 20)), Matrix2.CreateScale(2), new BoundingRectangle(new Point2(0, 0), new Size2(40, 40))).SetName(
+// "The bounding rectangle created from a non-empty bounding rectangle transformed by a non-identity matrix is the expected bounding rectangle.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CreateFromTransformedTestCases))]
+// public void CreateFromTransformed(BoundingRectangle boundingRectangle, Matrix2 transformMatrix,
+// BoundingRectangle expectedBoundingRectangle)
+// {
+// var actualBoundingRectangle = BoundingRectangle.Transform(boundingRectangle, ref transformMatrix);
+// Assert.Equal(expectedBoundingRectangle, actualBoundingRectangle);
+// }
+
+// public IEnumerable<TestCaseData> UnionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), new BoundingRectangle()).SetName(
+// "The union of two empty bounding rectangles is the empty bounding rectangle.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)),
+// new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)), new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)))
+// .SetName(
+// "The union of two non-empty bounding rectangles is the expected bounding rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(UnionTestCases))]
+// public void Union(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, BoundingRectangle expectedBoundingRectangle)
+// {
+// Assert.Equal(expectedBoundingRectangle, boundingRectangle1.Union(boundingRectangle2));
+// Assert.Equal(expectedBoundingRectangle, BoundingRectangle.Union(boundingRectangle1, boundingRectangle2));
+// }
+
+// public IEnumerable<TestCaseData> IntersectionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), new BoundingRectangle()).SetName(
+// "The intersection of two empty bounding rectangles is the empty bounding box.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(-10, -10), new Size2(15, 15)),
+// new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)),
+// new BoundingRectangle(new Point2(-7.5f, -7.5f), new Size2(12.5f, 12.5f))).SetName(
+// "The intersection of two overlapping non-empty bounding rectangles is the expected bounding rectangle.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(-30, -30), new Size2(15, 15)),
+// new BoundingRectangle(new Point2(20, 20), new Size2(10, 10)),
+// BoundingRectangle.Empty).SetName(
+// "The intersection of two non-overlapping non-empty bounding rectangles is the empty bounding rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectionTestCases))]
+// public void Intersection(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2,
+// BoundingRectangle? expectedBoundingRectangle)
+// {
+// Assert.Equal(expectedBoundingRectangle, boundingRectangle1.Intersection(boundingRectangle2));
+// Assert.Equal(expectedBoundingRectangle, BoundingRectangle.Intersection(boundingRectangle1, boundingRectangle2));
+// }
+
+// public IEnumerable<TestCaseData> IntersectsTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
+// "Two empty bounding rectangles intersect.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(-10, -10), new Size2(15, 15)),
+// new BoundingRectangle(new Point2(20, 20), new Size2(40, 40)), true).SetName(
+// "Two overlapping non-empty bounding rectangles intersect.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(-40, -50), new Size2(15, 15)),
+// new BoundingRectangle(new Point2(20, 20), new Size2(15, 15)), false).SetName(
+// "Two non-overlapping non-empty bounding rectangles do not intersect.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectsTestCases))]
+// public void Intersects(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedToIntersect)
+// {
+// Assert.Equal(expectedToIntersect, boundingRectangle1.Intersects(boundingRectangle2));
+// Assert.Equal(expectedToIntersect, BoundingRectangle.Intersects(boundingRectangle1, boundingRectangle2));
+// }
+
+// public IEnumerable<TestCaseData> ContainsPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new Point2(), true).SetName(
+// "The empty bounding rectangle contains the zero point.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)), new Point2(-15, -15), true)
+// .SetName(
+// "A non-empty bounding rectangle contains a point inside it.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(15, 15)), new Point2(-16, 15), false)
+// .SetName(
+// "A non-empty bounding rectangle does not contain a point outside it.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ContainsPointTestCases))]
+// public void ContainsPoint(BoundingRectangle boundingRectangle, Point2 point, bool expectedToContainPoint)
+// {
+// Assert.Equal(expectedToContainPoint, boundingRectangle.Contains(point));
+// Assert.Equal(expectedToContainPoint, BoundingRectangle.Contains(boundingRectangle, point));
+// }
+
+// public IEnumerable<TestCaseData> ClosestPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new Point2(), new Point2()).SetName(
+// "The closest point on the empty bounding rectangle to the zero point is the zero point.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Point2(50, 50)), new Point2(25, 25),
+// new Point2(25, 25)).SetName(
+// "The closest point on a non-empty bounding rectangle to a point which is inside the bounding rectangle is that point.")
+// ;
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Point2(50, 50)), new Point2(400, 0),
+// new Point2(50, 0)).SetName(
+// "The closest point on a non-empty bounding rectangle to a point which is outside the bounding rectangle is the expected point.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ClosestPointTestCases))]
+// public void ClosestPoint(BoundingRectangle boundingRectangle, Point2 point, Point2 expectedClosestPoint)
+// {
+// var actualClosestPoint = boundingRectangle.ClosestPointTo(point);
+// Assert.Equal(expectedClosestPoint, actualClosestPoint);
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
+// "Empty bounding rectangles are equal.")
+// ;
+// yield return
+// new TestCaseData(
+// new BoundingRectangle(new Point2(0, 0), new Size2(float.MaxValue, float.MinValue)),
+// new BoundingRectangle(new Point2(0, 0),
+// new Point2(float.MinValue, float.MaxValue)), false).SetName(
+// "Two different non-empty bounding rectangles are not equal.");
+// yield return
+// new TestCaseData(
+// new BoundingRectangle(new Point2(0, 0), new Size2(float.MinValue, float.MaxValue)),
+// new BoundingRectangle(new Point2(0, 0),
+// new Size2(float.MinValue, float.MaxValue)), true).SetName(
+// "Two identical non-empty bounding rectangles are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(boundingRectangle1, boundingRectangle2) == expectedToBeEqual);
+// Assert.IsTrue(boundingRectangle1 == boundingRectangle2 == expectedToBeEqual);
+// Assert.IsFalse(boundingRectangle1 == boundingRectangle2 != expectedToBeEqual);
+// Assert.IsTrue(boundingRectangle1.Equals(boundingRectangle2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(boundingRectangle1.GetHashCode(), boundingRectangle2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), null, false).SetName(
+// "A bounding rectangle is not equal to a null object.");
+// yield return
+// new TestCaseData(new BoundingRectangle(), new object(), false).SetName(
+// "A bounding rectangle is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(BoundingRectangle boundingRectangle, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(boundingRectangle.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new BoundingRectangle(), true).SetName(
+// "Two empty bounding rectangles have the same hash code.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)),
+// new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)), true).SetName(
+// "Two indentical non-empty bounding rectangles have the same hash code.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(0, 0), new Size2(50, 50)),
+// new BoundingRectangle(new Point2(50, 50), new Size2(50, 50)), false).SetName(
+// "Two different non-empty bounding rectangles do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(BoundingRectangle boundingRectangle1, BoundingRectangle boundingRectangle2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = boundingRectangle1.GetHashCode();
+// var hashCode2 = boundingRectangle2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> ToRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(), new Rectangle()).SetName(
+// "The empty bounding rectangle point converted to a rectangle is the empty rectangle.");
+// yield return
+// new TestCaseData(new BoundingRectangle(new Point2(25, 25), new Size2(25, 25)),
+// new Rectangle(0, 0, 50, 50)).SetName(
+// "A non-empty bounding rectangle converted to a rectangle is the expected rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToRectangleTestCases))]
+// public void ToRectangle(BoundingRectangle boundingRectangle, Rectangle expectedRectangle)
+// {
+// var actualRectangle = (Rectangle)boundingRectangle;
+// Assert.Equal(expectedRectangle, actualRectangle);
+// }
+
+// public IEnumerable<TestCaseData> FromRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Rectangle(), new BoundingRectangle()).SetName(
+// "The empty rectangle converted to a bounding rectangle is the empty bounding rectangle.");
+// yield return
+// new TestCaseData(new Rectangle(0, 0, 50, 50),
+// new BoundingRectangle(new Point2(25, 25), new Size2(25, 25))).SetName(
+// "A non-empty rectangle converted to a bounding rectangle is the expected bounding rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromRectangleTestCases))]
+// public void FromRectangle(Rectangle rectangle, BoundingRectangle expectedBoundingRectangle)
+// {
+// var actualBoundingRectangle = (BoundingRectangle)rectangle;
+// Assert.Equal(expectedBoundingRectangle, actualBoundingRectangle);
+// }
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new BoundingRectangle(),
+// string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radii: {1}", new Point2(),
+// new Vector2())).SetName(
+// "The empty bounding rectangle has the expected string representation using the current culture.");
+// yield return new TestCaseData(new BoundingRectangle(new Point2(5.1f, -5.123f), new Size2(5.4f, -5.4123f)),
+// string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radii: {1}", new Point2(5.1f, -5.123f),
+// new Vector2(5.4f, -5.4123f))).SetName(
+// "A non-empty bounding rectangle has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(BoundingRectangle boundingRectangle, string expectedString)
+// {
+// var actualString = boundingRectangle.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/CircleFTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/CircleFTests.cs
new file mode 100644
index 0000000..f88155f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/CircleFTests.cs
@@ -0,0 +1,420 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Primitives
+{
+
+public class CircleFTests
+{
+
+ [Fact]
+ public void CircCircIntersectionDiagonalCircleTest()
+ {
+ var circle = new CircleF(new Point2(16.0f, 16.0f), 16.0f);
+ var point = new Point2(0, 0);
+
+ Assert.False(circle.Contains(point));
+ }
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), 0.0f).SetName(
+// "The empty circle has the expected position and radius.");
+// yield return
+// new TestCaseData(new Point2(5, 5), 15f).SetName(
+// "A non-empty circle has the expected position and radius.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(Point2 centre, float radius)
+// {
+// var circle = new CircleF(centre, radius);
+// Assert.Equal(centre, circle.Center);
+// Assert.Equal(radius, circle.Radius);
+// }
+
+// public IEnumerable<TestCaseData> CreateFromMinimumMaximumTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), new CircleF()).SetName(
+// "The bounding circle created from the zero minimum point and zero maximum point is the empty bounding circle.")
+// ;
+// yield return
+// new TestCaseData(new Point2(5, 5), new Point2(15, 15),
+// new CircleF(new Point2(10, 10), 5f)).SetName(
+// "The bounding circle created from the non-zero minimum point and the non-zero maximum point is the expected bounding circle.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CreateFromMinimumMaximumTestCases))]
+// public void CreateFromMinimumMaximum(Point2 minimum, Point2 maximum, CircleF expectedBoundingCircle)
+// {
+// var actualBoundingCircle = CircleF.CreateFrom(minimum, maximum);
+// Assert.Equal(expectedBoundingCircle, actualBoundingCircle);
+// }
+
+// public IEnumerable<TestCaseData> CreateFromPointsTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(null, new CircleF()).SetName(
+// "The bounding circle created from null points is the empty bounding circle.");
+// yield return
+// new TestCaseData(new Point2[0], new CircleF()).SetName(
+// "The bounding circle created from the empty set of points is the empty bounding circle.");
+// yield return
+// new TestCaseData(
+// new[]
+// {
+// new Point2(5, 5), new Point2(10, 10), new Point2(15, 15), new Point2(-5, -5),
+// new Point2(-15, -15)
+// }, new CircleF(new Point2(0, 0), 15)).SetName(
+// "The bounding circle created from a non-empty set of points is the expected bounding circle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CreateFromPointsTestCases))]
+// public void CreateFromPoints(Point2[] points, CircleF expectedCircle)
+// {
+// var actualCircle = CircleF.CreateFrom(points);
+// Assert.Equal(expectedCircle, actualCircle);
+// }
+
+// public IEnumerable<TestCaseData> IntersectsCircleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new CircleF(), true).SetName(
+// "Two empty circles intersect.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(-10, -10), 15),
+// new CircleF(new Point2(20, 20), 40), true).SetName(
+// "Two overlapping non-empty circles intersect.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(-40, -50), 15),
+// new CircleF(new Point2(20, 20), 15), false).SetName(
+// "Two non-overlapping non-empty circles do not intersect.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectsCircleTestCases))]
+// public void Intersects(CircleF circle, CircleF circle2, bool expectedToIntersect)
+// {
+// Assert.Equal(expectedToIntersect, circle.Intersects(circle2));
+// Assert.Equal(expectedToIntersect, CircleF.Intersects(circle, circle2));
+// }
+
+// public IEnumerable<TestCaseData> IntersectsRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new RectangleF(), true).SetName(
+// "The empty circle and the empty rectangle intersect.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 15),
+// new RectangleF(new Point2(0, 0), new Size2(40, 40)), true).SetName(
+// "The non-empty circle and a non-empty overlapping rectangle intersect.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(-40, -50), 15),
+// new RectangleF(new Point2(20, 20), new Size2(15, 15)), false).SetName(
+// "The non-empty circle and a non-empty non-overlapping rectangle do not intersect.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectsRectangleTestCases))]
+// public void Intersects(CircleF circle, RectangleF rectangle, bool expectedToIntersect)
+// {
+// Assert.Equal(expectedToIntersect, circle.Intersects((BoundingRectangle)rectangle));
+// Assert.Equal(expectedToIntersect, CircleF.Intersects(circle, (BoundingRectangle)rectangle));
+// }
+
+// public IEnumerable<TestCaseData> ContainsPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new Point2(), true).SetName(
+// "The empty circle contains the zero point.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 15), new Point2(-15, -15), true)
+// .SetName(
+// "A non-empty circle contains a point inside it.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 15), new Point2(-16, 15), false)
+// .SetName(
+// "A non-empty circle does not contain a point outside it.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ContainsPointTestCases))]
+// public void ContainsPoint(CircleF circle, Point2 point, bool expectedToContainPoint)
+// {
+// Assert.Equal(expectedToContainPoint, circle.Contains(point));
+// Assert.Equal(expectedToContainPoint, CircleF.Contains(circle, point));
+// }
+
+// public IEnumerable<TestCaseData> ClosestPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new Point2(), new Point2()).SetName(
+// "The closest point on the empty circle to the zero point is the zero point.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 50), new Point2(25, 25),
+// new Point2(25, 25)).SetName(
+// "The closest point on a non-empty circle to a point which is inside the circle is that point.")
+// ;
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 50), new Point2(400, 0),
+// new Point2(50, 0)).SetName(
+// "The closest point on a non-empty circle to a point which is outside the circle is the expected point.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ClosestPointTestCases))]
+// public void ClosestPoint(CircleF circle, Point2 point, Point2 expectedClosestPoint)
+// {
+// var actualClosestPoint = circle.ClosestPointTo(point);
+// Assert.Equal(expectedClosestPoint, actualClosestPoint);
+// }
+
+// public IEnumerable<TestCaseData> BoundaryPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), 0.0f, new Point2()).SetName(
+// "The boundary point on the empty circle at an angle is the zero point.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 50), MathHelper.PiOver2,
+// new Point2(0, 50)).SetName(
+// "The boundary point on a non-empty circle at an angle is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(BoundaryPointTestCases))]
+// public void BoundaryPointAt(CircleF circle, float angle, Point2 expectedPoint)
+// {
+// var actualPoint = circle.BoundaryPointAt(angle);
+// AssertExtensions.AreApproximatelyEqual(expectedPoint, actualPoint);
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new CircleF(), true).SetName(
+// "Empty circles are equal.")
+// ;
+// yield return
+// new TestCaseData(
+// new CircleF(new Point2(0, 0), float.MaxValue),
+// new CircleF(new Point2(0, 0), float.MinValue), false).SetName(
+// "Two different non-empty circles are not equal.");
+// yield return
+// new TestCaseData(
+// new CircleF(new Point2(0, 0), float.MinValue),
+// new CircleF(new Point2(0, 0), float.MinValue), true).SetName(
+// "Two identical non-empty circles are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(CircleF circle1, CircleF circle2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(circle1, circle2) == expectedToBeEqual);
+// Assert.IsTrue(circle1 == circle2 == expectedToBeEqual);
+// Assert.IsFalse(circle1 == circle2 != expectedToBeEqual);
+// Assert.IsTrue(circle1.Equals(circle2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(circle1.GetHashCode(), circle2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), null, false).SetName(
+// "A circle is not equal to a null object.");
+// yield return
+// new TestCaseData(new CircleF(), new object(), false).SetName(
+// "A circle is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(CircleF circle, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(circle.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new CircleF(), true).SetName(
+// "Two empty circles have the same hash code.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 50),
+// new CircleF(new Point2(0, 0), 50), true).SetName(
+// "Two indentical non-empty circles have the same hash code.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(0, 0), 50),
+// new CircleF(new Point2(50, 50), 50), false).SetName(
+// "Two different non-empty circles do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(CircleF circle1, CircleF circle2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = circle1.GetHashCode();
+// var hashCode2 = circle2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> ToRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new Rectangle()).SetName(
+// "The empty circle converted to a rectangle is the empty integer rectangle.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(25, 25), 25),
+// new Rectangle(0, 0, 50, 50)).SetName(
+// "A non-empty circle converted to a rectangle is the expected integer rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToRectangleTestCases))]
+// public void ToRectangle(CircleF circle, Rectangle expectedRectangle)
+// {
+// var actualRectangle = (Rectangle)circle;
+// Assert.Equal(expectedRectangle, actualRectangle);
+// }
+
+// public IEnumerable<TestCaseData> ToRectangleFTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(), new RectangleF()).SetName(
+// "The empty circle converted to a rectangle is the empty float rectangle.");
+// yield return
+// new TestCaseData(new CircleF(new Point2(25, 25), 25),
+// new RectangleF(0, 0, 50, 50)).SetName(
+// "A non-empty circle converted to a rectangle is the expected float rectangle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToRectangleFTestCases))]
+// public void ToRectangleF(CircleF circle, RectangleF expectedRectangle)
+// {
+// var actualRectangle = (RectangleF)circle;
+// Assert.Equal(expectedRectangle, actualRectangle);
+// }
+
+// public IEnumerable<TestCaseData> FromRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Rectangle(), new CircleF()).SetName(
+// "The empty rectangle converted to a circle is the empty circle.");
+// yield return
+// new TestCaseData(new Rectangle(0, 0, 50, 50),
+// new CircleF(new Point2(25, 25), 25)).SetName(
+// "A non-empty rectangle converted to a circle is the expected circle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromRectangleTestCases))]
+// public void FromRectangle(Rectangle rectangle, CircleF expectedCircle)
+// {
+// var actualCircle = (CircleF)rectangle;
+// Assert.Equal(expectedCircle, actualCircle);
+// }
+
+// public IEnumerable<TestCaseData> FromRectangleFTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new RectangleF(), new CircleF()).SetName(
+// "The empty rectangle converted to a circle is the empty circle.");
+// yield return
+// new TestCaseData(new RectangleF(0, 0, 50, 50),
+// new CircleF(new Point2(25, 25), 25)).SetName(
+// "A non-empty rectangle converted to a circle is the expected circle.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromRectangleFTestCases))]
+// public void FromRectangleF(RectangleF rectangle, CircleF expectedCircle)
+// {
+// var actualCircle = (CircleF)rectangle;
+// Assert.Equal(expectedCircle, actualCircle);
+// }
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new CircleF(),
+// string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radius: {1}", new Point2(),
+// 0)).SetName(
+// "The empty circle has the expected string representation using the current culture.");
+// yield return new TestCaseData(new CircleF(new Point2(5.1f, -5.123f), 5.4f),
+// string.Format(CultureInfo.CurrentCulture, "Centre: {0}, Radius: {1}", new Point2(5.1f, -5.123f),
+// 5.4f)).SetName(
+// "A non-empty circle has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(CircleF circle, string expectedString)
+// {
+// var actualString = circle.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/EllipseFTest.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/EllipseFTest.cs
new file mode 100644
index 0000000..4a4ecdf
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/EllipseFTest.cs
@@ -0,0 +1,38 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Primitives
+{
+
+ public class EllipseFTest
+ {
+ [Theory]
+ [InlineData(-1, -1, false)]
+ [InlineData(110, 300, true)]
+ [InlineData(200, 300, true)]
+ [InlineData(290, 300, true)]
+ [InlineData(400, 400, false)]
+ public void ContainsPoint_Circle(int x, int y, bool expected)
+ {
+ var ellipse = new EllipseF(new Vector2(200.0f, 300.0f), 100.0f, 100.0f);
+
+ Assert.Equal(expected, ellipse.Contains(x, y));
+ }
+
+ [Theory]
+ [InlineData(299, 400, false)]
+ [InlineData(501, 400, false)]
+ [InlineData(400, 199, false)]
+ [InlineData(400, 601, false)]
+ [InlineData(301, 400, true)]
+ [InlineData(499, 400, true)]
+ [InlineData(400, 201, true)]
+ [InlineData(400, 599, true)]
+ public void ContainsPoint_NonCircle(int x, int y, bool expected)
+ {
+ var ellipse = new EllipseF(new Vector2(400.0f, 400.0f), 100.0f, 200.0f);
+
+ Assert.Equal(expected, ellipse.Contains(x, y));
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs
new file mode 100644
index 0000000..e3b40c8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs
@@ -0,0 +1,234 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Xunit;
+using Vector2 = Microsoft.Xna.Framework.Vector2;
+
+namespace MonoGame.Extended.Tests.Primitives;
+
+public class OrientedRectangleTests
+{
+ [Fact]
+ public void Initializes_oriented_rectangle()
+ {
+ var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10));
+
+ Assert.Equal(new Point2(1, 2), rectangle.Center);
+ Assert.Equal(new Vector2(3, 4), rectangle.Radii);
+ Assert.Equal(new Matrix2(5, 6, 7, 8, 9, 10), rectangle.Orientation);
+ CollectionAssert.Equal(
+ new List<Vector2>
+ {
+ new(-3, -2),
+ new(-33, -38),
+ new(23, 26),
+ new(53, 62)
+ },
+ rectangle.Points);
+ }
+
+ public static readonly IEnumerable<object[]> _equalsComparisons = new[]
+ {
+ new object[]
+ {
+ "empty compared with empty is true",
+ new OrientedRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity),
+ new OrientedRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity)
+ },
+ new object[]
+ {
+ "initialized compared with initialized true",
+ new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)),
+ new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10))
+ }
+ };
+
+ [Theory]
+ [MemberData(nameof(_equalsComparisons))]
+#pragma warning disable xUnit1026
+ public void Equals_comparison(string name, OrientedRectangle first, OrientedRectangle second)
+#pragma warning restore xUnit1026
+ {
+ Assert.True(first == second);
+ Assert.False(first != second);
+ }
+
+ public class Transform
+ {
+ [Fact]
+ public void Center_point_is_not_translated()
+ {
+ var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity);
+ var transform = Matrix2.Identity;
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Point2(1, 2), result.Center);
+ }
+
+ [Fact]
+ public void Center_point_is_translated()
+ {
+ var rectangle = new OrientedRectangle(new Point2(0, 0), new Size2(), new Matrix2());
+ var transform = Matrix2.CreateTranslation(1, 2);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Point2(1, 2), result.Center);
+ }
+
+ [Fact]
+ public void Radii_is_not_changed_by_identity_transform()
+ {
+ var rectangle = new OrientedRectangle(new Point2(), new Size2(10, 20), new Matrix2());
+ var transform = Matrix2.Identity;
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Vector2(10, 20), result.Radii);
+ }
+
+ [Fact]
+ public void Radii_is_not_changed_by_translation()
+ {
+ var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(10, 20), new Matrix2());
+ var transform = Matrix2.CreateTranslation(1, 2);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Vector2(10, 20), result.Radii);
+ }
+
+ [Fact]
+ public void Orientation_is_rotated_45_degrees_left()
+ {
+ var rectangle = new OrientedRectangle(new Point2(), new Size2(), Matrix2.Identity);
+ var transform = Matrix2.CreateRotationZ(MathHelper.PiOver4);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Point2(), result.Center);
+ Assert.Equal(new Vector2(), result.Radii);
+ Assert.Equal(Matrix2.CreateRotationZ(MathHelper.PiOver4), result.Orientation);
+ }
+
+ [Fact]
+ public void Orientation_is_rotated_to_45_degrees_from_180()
+ {
+ var rectangle = new OrientedRectangle(new Point2(), new Size2(), Matrix2.CreateRotationZ(MathHelper.Pi));
+ var transform = Matrix2.CreateRotationZ(-3 * MathHelper.PiOver4);
+ var expectedOrientation = Matrix2.CreateRotationZ(MathHelper.PiOver4);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Point2(), result.Center);
+ Assert.Equal(new Vector2(), result.Radii);
+ Assert.Equal(expectedOrientation.M11, result.Orientation.M11, 6);
+ Assert.Equal(expectedOrientation.M12, result.Orientation.M12, 6);
+ Assert.Equal(expectedOrientation.M21, result.Orientation.M21, 6);
+ Assert.Equal(expectedOrientation.M22, result.Orientation.M22, 6);
+ Assert.Equal(expectedOrientation.M31, result.Orientation.M31, 6);
+ Assert.Equal(expectedOrientation.M32, result.Orientation.M32, 6);
+ }
+
+ [Fact]
+ public void Points_are_same_as_center()
+ {
+ var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity);
+ var transform = Matrix2.Identity;
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ CollectionAssert.Equal(
+ new List<Vector2>
+ {
+ new(1, 2),
+ new(1, 2),
+ new(1, 2),
+ new(1, 2)
+ },
+ result.Points);
+ }
+
+ [Fact]
+ public void Points_are_translated()
+ {
+ var rectangle = new OrientedRectangle(new Point2(0, 0), new Size2(2, 4), Matrix2.Identity);
+ var transform = Matrix2.CreateTranslation(10, 20);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ CollectionAssert.Equal(
+ new List<Vector2>
+ {
+ new(8, 16),
+ new(8, 24),
+ new(12, 24),
+ new(12, 16)
+ },
+ result.Points);
+ }
+
+ [Fact]
+ public void Applies_rotation_and_translation()
+ {
+ /* Rectangle with center point p, aligned in coordinate system with origin 0.
+ *
+ * :
+ * :
+ * +-+
+ * | |
+ * |p|
+ * | |
+ * ...............0-+............
+ * :
+ * :
+ * :
+ *
+ * Rotate around center point p, 90 degrees around origin 0.
+ *
+ * :
+ * :
+ * +---+
+ * | p |
+ * ...........+---0..............
+ * :
+ * :
+ * :
+ *
+ * Then translate rectangle by x=10 and y=20.
+ * :
+ * : +---+
+ * : | p |
+ * y=21 - - - - - - - -> +---+
+ * .
+ * :
+ * ...............0..............
+ * :
+ * :
+ * :
+ */
+ var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(2, 4), Matrix2.Identity);
+ var transform =
+ Matrix2.CreateRotationZ(MathHelper.PiOver2)
+ *
+ Matrix2.CreateTranslation(10, 20);
+
+ var result = OrientedRectangle.Transform(rectangle, ref transform);
+
+ Assert.Equal(8, result.Center.X, 6);
+ Assert.Equal(21, result.Center.Y, 6);
+ Assert.Equal(2, result.Radii.X, 6);
+ Assert.Equal(4, result.Radii.Y, 6);
+ Assert.Equal(Matrix2.CreateRotationZ(MathHelper.PiOver2), result.Orientation);
+ CollectionAssert.Equal(
+ new List<Vector2>
+ {
+ new(4, 23),
+ new(4, 19),
+ new(12, 19),
+ new(12, 23)
+ },
+ result.Points);
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Point2Tests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Point2Tests.cs
new file mode 100644
index 0000000..ce5f89b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Point2Tests.cs
@@ -0,0 +1,356 @@
+//using System.Collections.Generic;
+//using System.Globalization;
+//using Microsoft.Xna.Framework;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Primitives
+//{
+//
+// public class Point2Tests
+// {
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(0, 0).SetName(
+// "The zero point has the expected coordinates.");
+// yield return
+// new TestCaseData(float.MinValue, float.MaxValue).SetName
+// (
+// "A non-zero point has the expected coordinates.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(float x, float y)
+// {
+// var point = new Point2(x, y);
+// Assert.Equal(x, point.X);
+// Assert.Equal(y, point.Y);
+// }
+
+// public IEnumerable<TestCaseData> CoordinatesTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), 0, 0).SetName(
+// "The zero point has the expected coordinates.");
+// yield return
+// new TestCaseData(new Point2(float.MinValue, float.MaxValue), float.MinValue, float.MaxValue).SetName
+// (
+// "A non-zero point has the expected coordinates.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(CoordinatesTestCases))]
+// public void Coordinates(Point2 point, float expectedX, float expecetedY)
+// {
+// Assert.Equal(expectedX, point.X);
+// Assert.Equal(expecetedY, point.Y);
+
+// point.X = 10;
+// Assert.Equal(10, point.X);
+
+// point.Y = -10.123f;
+// Assert.Equal(-10.123f, point.Y);
+// }
+
+// public IEnumerable<TestCaseData> VectorAdditionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Vector2(), new Point2()).SetName(
+// "The addition of the zero point and the zero vector is the zero point.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Vector2(15, 15), new Point2(20, 20)).SetName(
+// "The addition of a non-zero point and a non-zero vector is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(VectorAdditionTestCases))]
+// public void VectorAddition(Point2 point, Vector2 vector, Point2 expectedPoint)
+// {
+// Assert.Equal(expectedPoint, point + vector);
+// Assert.Equal(expectedPoint, Point2.Add(point, vector));
+// }
+
+// public IEnumerable<TestCaseData> VectorSubtractionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Vector2(), new Point2()).SetName(
+// "The vector subtraction of two zero points is the zero vector.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Vector2(15, 15), new Point2(-10, -10)).SetName(
+// "The vector subtraction of two non-zero points is the expected vector.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(VectorSubtractionTestCases))]
+// public void VectorSubtraction(Point2 point, Vector2 vector, Point2 expectedPoint)
+// {
+// Assert.Equal(expectedPoint, point - vector);
+// Assert.Equal(expectedPoint, Point2.Subtract(point, vector));
+// }
+
+
+// public IEnumerable<TestCaseData> DisplacementTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), new Vector2()).SetName(
+// "The displacement between two zero points is the zero vector.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Point2(15, 15), new Vector2(10, 10)).SetName(
+// "The displacement between two non-zero points is the expected vector.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(DisplacementTestCases))]
+// public void Displacement(Point2 point1, Point2 point2, Vector2 expectedVector)
+// {
+// Assert.Equal(expectedVector, point2 - point1);
+// Assert.Equal(expectedVector, Point2.Displacement(point2, point1));
+// }
+
+// public IEnumerable<TestCaseData> SizeAdditionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Size2(), new Point2()).SetName(
+// "The size addition of the zero point with the empty size is the zero point.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Size2(15, 15), new Point2(20, 20)).SetName(
+// "The size addition of a non-zero point with a non-empty size is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(SizeAdditionTestCases))]
+// public void SizeAdditon(Point2 point, Size2 size, Point2 expectedPoint)
+// {
+// Assert.Equal(expectedPoint, point + size);
+// Assert.Equal(expectedPoint, Point2.Add(point, size));
+// }
+
+// public IEnumerable<TestCaseData> SizeSubtractionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Size2(), new Point2()).SetName(
+// "The size substraction of the zero point with the empty size is the zero point.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Size2(15, 15), new Point2(-10, -10)).SetName(
+// "The size subscration of a non-zero point with a non-empty size is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(SizeSubtractionTestCases))]
+// public void SizeSubtraction(Point2 point, Size2 size, Point2 expectedPoint)
+// {
+// Assert.Equal(expectedPoint, point - size);
+// Assert.Equal(expectedPoint, Point2.Subtract(point, size));
+// }
+
+// public IEnumerable<TestCaseData> MinimumTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), new Point2()).SetName(
+// "The minimum coordinates of two zero points is the coordinates of the zero point.");
+// yield return
+// new TestCaseData(new Point2(float.MaxValue, float.MinValue), new Point2(int.MaxValue, int.MinValue),
+// new Point2(int.MaxValue, float.MinValue)).SetName(
+// "The minimum coordaintes of two non-zero points is the expected coordinates.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(MinimumTestCases))]
+// public void Minimum(Point2 point1, Point2 point2, Point2 expectedPoint)
+// {
+// var actualPoint = Point2.Minimum(point1, point2);
+// Assert.Equal(expectedPoint, actualPoint);
+// }
+
+// public IEnumerable<TestCaseData> MaximumTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), new Point2()).SetName(
+// "The maximum coordinates of two zero points is the coordinates of the zero point.");
+// yield return
+// new TestCaseData(new Point2(float.MaxValue, float.MinValue), new Point2(int.MaxValue, int.MinValue),
+// new Point2(float.MaxValue, int.MinValue)).SetName(
+// "The maximum coordaintes of two non-zero points is the expected coordinates.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(MaximumTestCases))]
+// public void Maximum(Point2 point1, Point2 point2, Point2 expectedPoint)
+// {
+// var actualPoint = Point2.Maximum(point1, point2);
+// Assert.Equal(expectedPoint, actualPoint);
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), true).SetName("Two zero points are equal.");
+// yield return
+// new TestCaseData(new Point2(float.MinValue, float.MaxValue),
+// new Point2(float.MaxValue, float.MinValue), false).SetName(
+// "Two different non-zero points are not equal.");
+// yield return
+// new TestCaseData(
+// new Point2(float.MinValue, float.MaxValue), new Point2(float.MinValue, float.MaxValue), true)
+// .SetName(
+// "Two identical non-zero points are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(Point2 point1, Point2 point2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(point1, point2) == expectedToBeEqual);
+// Assert.IsTrue(point1 == point2 == expectedToBeEqual);
+// Assert.IsFalse(point1 == point2 != expectedToBeEqual);
+// Assert.IsTrue(point1.Equals(point2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(point1.GetHashCode(), point2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), null, false).SetName("A point is not equal to a null object.");
+// yield return
+// new TestCaseData(new Point2(), new object(), false).SetName(
+// "A point is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(Point2 point, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(point.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2(), true).SetName(
+// "Two zero points have the same hash code.");
+// yield return
+// new TestCaseData(new Point2(50, 50), new Point2(50, 50), true).SetName(
+// "Two indentical non-zero points have the same hash code.");
+// yield return
+// new TestCaseData(new Point2(0, 0), new Point2(50, 50), false).SetName(
+// "Two different non-zero points do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(Point2 point1, Point2 point2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = point1.GetHashCode();
+// var hashCode2 = point2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> ToVectorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Vector2()).SetName(
+// "The zero point converted to a vector is the zero vector.");
+// yield return
+// new TestCaseData(new Point2(float.MinValue, float.MaxValue),
+// new Vector2(float.MinValue, float.MaxValue)).SetName(
+// "A non-zero point converted to a vector is the expected vector.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToVectorTestCases))]
+// public void ToVector(Point2 point, Vector2 expectedVector)
+// {
+// var actualVector = (Vector2)point;
+// Assert.Equal(expectedVector, actualVector);
+// }
+
+// public IEnumerable<TestCaseData> FromVectorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Vector2(), new Point2()).SetName(
+// "The zero vector converted to a point is the zero point.");
+// yield return
+// new TestCaseData(new Vector2(float.MinValue, float.MaxValue),
+// new Point2(float.MinValue, float.MaxValue)).SetName(
+// "A non-zero vector converted to a point is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromVectorTestCases))]
+// public void FromVector(Vector2 vector, Point2 expectedPoint)
+// {
+// var actualPoint = (Point2)vector;
+// Assert.Equal(expectedPoint, actualPoint);
+// }
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(),
+// string.Format(CultureInfo.CurrentCulture, "({0}, {1})", 0, 0)).SetName(
+// "The zero point has the expected string representation using the current culture.");
+// yield return new TestCaseData(new Point2(5.1f, -5.123f),
+// string.Format(CultureInfo.CurrentCulture, "({0}, {1})", 5.1f, -5.123f)).SetName(
+// "A non-zero point has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(Point2 point, string expectedString)
+// {
+// var actualString = point.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Ray2DTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Ray2DTests.cs
new file mode 100644
index 0000000..04e8b00
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Ray2DTests.cs
@@ -0,0 +1,217 @@
+//using System.Collections.Generic;
+//using System.Globalization;
+//using Microsoft.Xna.Framework;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Primitives
+//{
+//
+// public class Ray2Tests
+// {
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Vector2()).SetName(
+// "The degenerate ray has the expected position and direction.");
+// yield return
+// new TestCaseData(new Point2(5, 5), new Vector2(15, 15)).SetName(
+// "A non-degenerate ray has the expected position and direction.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(Point2 position, Vector2 direction)
+// {
+// var ray = new Ray2(position, direction);
+// Assert.Equal(position, ray.Position);
+// Assert.Equal(direction, ray.Direction);
+// }
+
+// public IEnumerable<TestCaseData> PositionDirectionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(), new Point2(), new Vector2()).SetName(
+// "The degenerate ray has the expected position and direction.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(5, 5), new Vector2(15, 15)), new Point2(5, 5),
+// new Vector2(15, 15)).SetName
+// (
+// "A non-degenerate ray has the expected position and direction.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(PositionDirectionTestCases))]
+// public void PositionDirection(Ray2 ray, Point2 expectedPosition, Vector2 expecetedDirection)
+// {
+// Assert.Equal(expectedPosition, ray.Position);
+// Assert.Equal(expecetedDirection, ray.Direction);
+
+// ray.Position.X = 10;
+// ray.Position.Y = 10;
+// Assert.Equal(new Point2(10, 10), ray.Position);
+
+// ray.Direction.X = -10.123f;
+// ray.Direction.Y = 10.123f;
+// Assert.Equal(new Vector2(-10.123f, 10.123f), ray.Direction);
+// }
+
+// public IEnumerable<TestCaseData> IntersectsBoundingRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(), new BoundingRectangle(), true, Point2.Zero, Point2.Zero).SetName(
+// "The degenerate ray intersects the empty bounding box.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(-75, -75), new Vector2(75, -75)),
+// new BoundingRectangle(new Point2(), new Size2(50, 50)), false, Point2.NaN, Point2.NaN).SetName(
+// "A non-degenerate ray that does not cross a non-empty bounding box does not intersect the bounding box.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(0, 0), new Vector2(25, 0)), new BoundingRectangle(new Point2(), new Size2(50, 50)),
+// true, new Point2(0, 0), new Point2(50, 0)).SetName(
+// "A non-degenerate ray starting from inside a non-empty bounding box intersects the bounding box.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(-100, 0), new Vector2(100, 0)),
+// new BoundingRectangle(new Point2(), new Size2(50, 50)),
+// true, new Point2(-50, 0), new Point2(50, 0)).SetName(
+// "A non-degenerate ray crossing a non-empty bounding box intersects the bounding box.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectsBoundingRectangleTestCases))]
+// public void IntersectsBoundingRectangle(Ray2 ray, BoundingRectangle boundingRectangle, bool expectedResult,
+// Point2 firstExpectedIntersectionPoint, Point2 secondExpectedIntersectionPoint)
+// {
+// float rayNearDistance, rayFarDistance;
+// var actualResult = ray.Intersects(boundingRectangle, out rayNearDistance, out rayFarDistance);
+// Assert.Equal(expectedResult, actualResult);
+
+// if (actualResult)
+// {
+// var firstActualIntersectionPoint = ray.Position + ray.Direction * rayNearDistance;
+// Assert.Equal(firstExpectedIntersectionPoint, firstActualIntersectionPoint);
+// var secondActualIntersectionPoint = ray.Position + ray.Direction * rayFarDistance;
+// Assert.Equal(secondExpectedIntersectionPoint, secondActualIntersectionPoint);
+// }
+// else
+// {
+// Assert.IsTrue(float.IsNaN(rayNearDistance));
+// Assert.IsTrue(float.IsNaN(rayFarDistance));
+// }
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(), new Ray2(), true).SetName("Two degenerate rays are equal.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(float.MinValue, float.MaxValue),
+// new Vector2(float.MaxValue, float.MinValue)), new Ray2(new Point2(float.MaxValue, float.MinValue),
+// new Vector2(float.MaxValue, float.MinValue)), false).SetName(
+// "Two different non-degenerate rays are not equal.");
+// yield return
+// new TestCaseData(
+// new Ray2(new Point2(float.MinValue, float.MaxValue),
+// new Vector2(float.MinValue, float.MaxValue)), new Ray2(new Point2(float.MinValue, float.MaxValue),
+// new Vector2(float.MinValue, float.MaxValue)), true)
+// .SetName(
+// "Two identical non-degenerate rays are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(Ray2 ray1, Ray2 ray2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(ray1, ray2) == expectedToBeEqual);
+// Assert.IsTrue(ray1 == ray2 == expectedToBeEqual);
+// Assert.IsFalse(ray1 == ray2 != expectedToBeEqual);
+// Assert.IsTrue(ray1.Equals(ray2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(ray1.GetHashCode(), ray2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(), null, false).SetName("A ray is not equal to a null object.");
+// yield return
+// new TestCaseData(new Ray2(), new object(), false).SetName(
+// "A ray is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(Ray2 ray, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(ray.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(), new Ray2(), true).SetName(
+// "Two degenerate rays have the same hash code.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(50, 50), new Vector2(50, 50)),
+// new Ray2(new Point2(50, 50), new Vector2(50, 50)), true).SetName(
+// "Two indentical non-zero points have the same hash code.");
+// yield return
+// new TestCaseData(new Ray2(new Point2(0, 0), new Vector2(50, 50)),
+// new Ray2(new Point2(50, 50), new Vector2(50, 50)), false).SetName(
+// "Two different non-zero points do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(Ray2 ray1, Ray2 ray2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = ray1.GetHashCode();
+// var hashCode2 = ray2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Ray2(),
+// string.Format(CultureInfo.CurrentCulture, "Position: {0}, Direction: {1}", new Point2(),
+// new Vector2())).SetName(
+// "The degenerate ray has the expected string representation using the current culture.");
+// yield return new TestCaseData(new Ray2(new Point2(5.1f, -5.123f), new Vector2(0, 1)),
+// string.Format(CultureInfo.CurrentCulture, "Position: {0}, Direction: {1}", new Point2(5.1f, -5.123f),
+// new Vector2(0, 1))).SetName(
+// "A non-degenerate ray has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(Ray2 ray, string expectedString)
+// {
+// var actualString = ray.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/RectangleFTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/RectangleFTests.cs
new file mode 100644
index 0000000..9121d6a
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/RectangleFTests.cs
@@ -0,0 +1,135 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Primitives
+{
+ public class RectangleFTests
+ {
+ [Fact]
+ public void Rectangle_Intersects_Test()
+ {
+ var rect1 = new Rectangle(0, 0, 32, 32);
+ var rect2 = new Rectangle(32, 32, 32, 32);
+
+ Assert.False(rect1.Intersects(rect2));
+ }
+
+ [Fact]
+ public void PassVector2AsConstructorParameter_Test()
+ {
+ var rect1 = new RectangleF(new Vector2(0, 0), new Size2(12.34f, 56.78f));
+ var rect2 = new RectangleF(new Vector2(0, 0), new Vector2(12.34f, 56.78f));
+
+ Assert.Equal(rect1, rect2);
+ }
+
+ [Fact]
+ public void PassPointAsConstructorParameter_Test()
+ {
+ var rect1 = new RectangleF(new Vector2(0, 0), new Size2(12, 56));
+ var rect2 = new RectangleF(new Vector2(0, 0), new Size2(12, 56));
+
+ Assert.Equal(rect1, rect2);
+ }
+
+ public class Transform
+ {
+ [Fact]
+ public void Center_point_is_not_translated()
+ {
+ var rectangle = new RectangleF(new Point2(0, 0), new Size2(20, 30));
+ var transform = Matrix2.Identity;
+
+ var result = RectangleF.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Point2(10, 15), result.Center);
+ }
+
+ [Fact]
+ public void Center_point_is_translated()
+ {
+ var rectangleF = new RectangleF(new Point2(0, 0), new Size2(20, 30));
+ var transform = Matrix2.CreateTranslation(1, 2);
+
+ var result = RectangleF.Transform(rectangleF, ref transform);
+
+ Assert.Equal(new Point2(11, 17), result.Center);
+ }
+
+ [Fact]
+ public void Size_is_not_changed_by_identity_transform()
+ {
+ var rectangle = new RectangleF(new Point2(0, 0), new Size2(20, 30));
+ var transform = Matrix2.Identity;
+
+ var result = RectangleF.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Size2(20, 30), result.Size);
+ }
+
+ [Fact]
+ public void Size_is_not_changed_by_translation()
+ {
+ var rectangle = new RectangleF(new Point2(0, 0), new Size2(20, 30));
+ var transform = Matrix2.CreateTranslation(1, 2);
+
+ var result = RectangleF.Transform(rectangle, ref transform);
+
+ Assert.Equal(new Size2(20, 30), result.Size);
+ }
+
+ [Fact]
+ public void Applies_rotation_and_translation()
+ {
+ /* Rectangle with center point aligned in coordinate system with origin 0.
+ *
+ * :
+ * :
+ * +-+
+ * | |
+ * |p|
+ * | |
+ * ...............0-+............
+ * :
+ * :
+ * :
+ *
+ * Rotate center point p, 90 degrees around origin 0.
+ *
+ * :
+ * :
+ * +---+
+ * | p |
+ * ...........+---0..............
+ * :
+ * :
+ * :
+ *
+ * Then translate rectangle by x=10 and y=20.
+ * :
+ * : +---+
+ * : | p |
+ * y=21 - - - - - - - -> +---+
+ * .
+ * :
+ * ...............0..............
+ * :
+ * :
+ * :
+ */
+ var rectangle = new RectangleF(new Point2(0, 0), new Size2(2, 4));
+ var transform =
+ Matrix2.CreateRotationZ(MathHelper.PiOver2)
+ *
+ Matrix2.CreateTranslation(10, 20);
+
+ var result = RectangleF.Transform(rectangle, ref transform);
+
+ Assert.Equal(-2 + 10, result.Center.X, 6);
+ Assert.Equal(1 + 20, result.Center.Y, 6);
+ Assert.Equal(4, result.Size.Width, 6);
+ Assert.Equal(2, result.Size.Height, 6);
+ }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Segment2DTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Segment2DTests.cs
new file mode 100644
index 0000000..b994527
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Segment2DTests.cs
@@ -0,0 +1,251 @@
+//using System.Collections.Generic;
+//using System.Globalization;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Primitives
+//{
+// public class Segment2DTests
+// {
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Point2()).SetName(
+// "The empty segment has expected starting and ending points.");
+// yield return
+// new TestCaseData(
+// new Point2(float.MaxValue, float.MinValue),
+// new Point2(int.MaxValue, int.MinValue)).SetName(
+// "A non-empty segment has the expected starting and ending points.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(Point2 startingPoint, Point2 endingPoint)
+// {
+// var segment = new Segment2(startingPoint, endingPoint);
+// Assert.Equal(startingPoint, segment.Start);
+// Assert.Equal(endingPoint, segment.End);
+// }
+
+// public IEnumerable<TestCaseData> ClosestPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), new Point2(), new Point2()).SetName(
+// "The closest point on the empty segment to the zero point is the zero point.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(200, 0)), new Point2(-100, 200),
+// new Point2(0, 0)).SetName(
+// "The closest point on a non-empty segment to a point which is projected beyond the start of the segment is the segment's starting point.")
+// ;
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(200, 0)), new Point2(400, 200),
+// new Point2(200, 0)).SetName(
+// "The closest point on a non-empty segment to a point which is projected beyond the end of the segment is the segment's ending point.")
+// ;
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(200, 0)), new Point2(100, 200),
+// new Point2(100, 0)).SetName(
+// "The closest point on a non-empty segment to a point which is projected inside the segment is the projected point.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ClosestPointTestCases))]
+// public void ClosestPoint(Segment2 segment, Point2 point, Point2 expectedClosestPoint)
+// {
+// var actualClosestPoint = segment.ClosestPointTo(point);
+// Assert.Equal(expectedClosestPoint, actualClosestPoint);
+// }
+
+// public IEnumerable<TestCaseData> SquaredDistanceToPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), new Point2(), 0).SetName(
+// "The squared distance of the zero point to the empty segment is 0.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(20, 0)), new Point2(-10, 20), 500)
+// .SetName(
+// "The squared distance of a point projected beyond the start of a non-empty segment is the squared distance from the segment's starting point to the point.")
+// ;
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(20, 0)), new Point2(40, 20), 400)
+// .SetName(
+// "The squared distance of a point projected beyond the end of a non-empty segment is the squared distance from the segment's ending point to the point.")
+// ;
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(20, 0)), new Point2(10, 25), 625).SetName
+// (
+// "The squared distance of a point projected inside a non-empty segment is the squared distance from the projected point to the point.")
+// ;
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(SquaredDistanceToPointTestCases))]
+// public void SquaredDistanceToPoint(Segment2 segment, Point2 point,
+// float expectedDistance)
+// {
+// var actualDistance = segment.SquaredDistanceTo(point);
+// Assert.Equal(expectedDistance, actualDistance);
+// }
+
+// public IEnumerable<TestCaseData> IntersectsBoundingRectangleTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), new BoundingRectangle(), true, Point2.Zero).SetName(
+// "The empty segment intersects the empty bounding box.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(-75, -75), new Point2(75, -75)),
+// new BoundingRectangle(new Point2(), new Size2(50, 50)), false, Point2.NaN).SetName(
+// "A non-empty segment outside a non-empty bounding box does not intersect the bounding box.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(25, 0)), new BoundingRectangle(new Point2(), new Size2(50, 50)),
+// true, new Point2(0, 0)).SetName(
+// "A non-empty segment inside a non-empty bounding box intersects the bounding box.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(-100, 0), new Point2(100, 0)),
+// new BoundingRectangle(new Point2(), new Size2(50, 50)),
+// true, new Point2(-50, 0)).SetName(
+// "A non-empty segment crossing a non-empty bounding box intersects the bounding box.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(IntersectsBoundingRectangleTestCases))]
+// public void IntersectsBoundingRectangle(Segment2 segment, BoundingRectangle boundingRectangle, bool expectedResult,
+// Point2 expectedIntersectionPoint)
+// {
+// Point2 actualIntersectionPoint;
+// var actualResult = segment.Intersects(boundingRectangle, out actualIntersectionPoint);
+// Assert.Equal(expectedResult, actualResult);
+
+// if (actualResult)
+// {
+// Assert.Equal(expectedIntersectionPoint, actualIntersectionPoint);
+// }
+// else
+// {
+// Assert.IsTrue(float.IsNaN(actualIntersectionPoint.X));
+// Assert.IsTrue(float.IsNaN(actualIntersectionPoint.Y));
+// }
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), new Segment2(), true).SetName("Empty segments are equal.")
+// ;
+// yield return
+// new TestCaseData(
+// new Segment2(new Point2(0, 0), new Point2(float.MaxValue, float.MinValue)),
+// new Segment2(new Point2(0, 0),
+// new Point2(float.MinValue, float.MaxValue)), false).SetName(
+// "Two different non-empty segments are not equal.");
+// yield return
+// new TestCaseData(
+// new Segment2(new Point2(0, 0), new Point2(float.MinValue, float.MaxValue)),
+// new Segment2(new Point2(0, 0),
+// new Point2(float.MinValue, float.MaxValue)), true).SetName(
+// "Two identical non-empty segments are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(Segment2 segment1, Segment2 segment2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(segment1, segment2) == expectedToBeEqual);
+// Assert.IsTrue(segment1 == segment2 == expectedToBeEqual);
+// Assert.IsFalse(segment1 == segment2 != expectedToBeEqual);
+// Assert.IsTrue(segment1.Equals(segment2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(segment1.GetHashCode(), segment2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), null, false).SetName("A segment is not equal to a null object.");
+// yield return
+// new TestCaseData(new Segment2(), new object(), false).SetName(
+// "A segment is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(Segment2 segment, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(segment.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(), new Segment2(), true).SetName(
+// "Two empty segments have the same hash code.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(50, 50)),
+// new Segment2(new Point2(0, 0), new Point2(50, 50)), true).SetName(
+// "Two indentical non-empty segments have the same hash code.");
+// yield return
+// new TestCaseData(new Segment2(new Point2(0, 0), new Point2(50, 50)),
+// new Segment2(new Point2(50, 50), new Point2(50, 50)), false).SetName(
+// "Two different non-empty segments do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(Segment2 segment1, Segment2 segment2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = segment1.GetHashCode();
+// var hashCode2 = segment2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Segment2(),
+// string.Format(CultureInfo.CurrentCulture, "{0} -> {1}", new Point2(),
+// new Point2())).SetName(
+// "The empty segment has the expected string representation using the current culture.");
+// yield return new TestCaseData(new Segment2(new Point2(5.1f, -5.123f), new Point2(5.4f, -5.4123f)),
+// string.Format(CultureInfo.CurrentCulture, "{0} -> {1}", new Point2(5.1f, -5.123f),
+// new Point2(5.4f, -5.4123f))).SetName(
+// "A non-empty segment has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(Segment2 segment, string expectedString)
+// {
+// var actualString = segment.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs
new file mode 100644
index 0000000..37b2fc4
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs
@@ -0,0 +1,180 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Primitives;
+
+public class ShapeTests
+{
+ public class CircleFTests
+ {
+ [Fact]
+ public void CircCircIntersectionSameCircleTest()
+ {
+ IShapeF shape1 = new CircleF(Point2.Zero, 2.0f);
+ IShapeF shape2 = new CircleF(Point2.Zero, 2.0f);
+
+ Assert.True(shape1.Intersects(shape2));
+ }
+
+ [Fact]
+ public void CircCircIntersectionOverlappingTest()
+ {
+ IShapeF shape1 = new CircleF(new Point2(1, 2), 2.0f);
+ IShapeF shape2 = new CircleF(Point2.Zero, 2.0f);
+
+ Assert.True(shape1.Intersects(shape2));
+ }
+
+ [Fact]
+ public void CircleCircleNotIntersectingTest()
+ {
+ IShapeF shape1 = new CircleF(new Point2(5, 5), 2.0f);
+ IShapeF shape2 = new CircleF(Point2.Zero, 2.0f);
+
+ Assert.False(shape1.Intersects(shape2));
+ }
+ }
+
+ public class RectangleFTests
+ {
+ [Fact]
+ public void RectRectSameRectTest()
+ {
+ IShapeF shape1 = new RectangleF(Point2.Zero, new Size2(5, 5));
+ IShapeF shape2 = new RectangleF(Point2.Zero, new Size2(5, 5));
+
+ Assert.True(shape1.Intersects(shape2));
+ }
+
+ [Fact]
+ public void RectRectIntersectingTest()
+ {
+ IShapeF shape1 = new RectangleF(Point2.Zero, new Size2(5, 5));
+ IShapeF shape2 = new RectangleF(new Point2(3, 3), new Size2(5, 5));
+
+ Assert.True(shape1.Intersects(shape2));
+ }
+
+ [Fact]
+ public void RectRectNotIntersectingTest()
+ {
+ IShapeF shape1 = new RectangleF(Point2.Zero, new Size2(5, 5));
+ IShapeF shape2 = new RectangleF(new Point2(10, 10), new Size2(5, 5));
+
+ Assert.False(shape1.Intersects(shape2));
+ }
+
+ [Fact]
+ public void RectCircContainedTest()
+ {
+ IShapeF shape1 = new RectangleF(Point2.Zero, new Size2(5, 5));
+ IShapeF shape2 = new CircleF(Point2.Zero, 4);
+
+ Assert.True(shape1.Intersects(shape2));
+ Assert.True(shape2.Intersects(shape1));
+ }
+
+ [Fact]
+ public void RectCircOnEdgeTest()
+ {
+ IShapeF shape1 = new RectangleF(Point2.Zero, new Size2(5, 5));
+ IShapeF shape2 = new CircleF(new Point2(5, 0), 4);
+
+ Assert.True(shape1.Intersects(shape2));
+ Assert.True(shape2.Intersects(shape1));
+ }
+ }
+
+ public class OrientedRectangleTests
+ {
+ [Fact]
+ public void Axis_aligned_rectangle_intersects_circle()
+ {
+ /*
+ * :
+ * :
+ * +*+
+ * ...........* *.........
+ * +*+
+ * :
+ * :
+ */
+ IShapeF rectangle = new OrientedRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity);
+ var circle = new CircleF(Point2.Zero, 1);
+
+ Assert.True(rectangle.Intersects(circle));
+ }
+
+ [Fact]
+ public void Axis_aligned_rectangle_does_not_intersect_circle_in_top_left()
+ {
+ /*
+ * * :
+ * * *:
+ * *+-+
+ * ...........| |.........
+ * +-+
+ * :
+ * :
+ */
+ IShapeF rectangle = new OrientedRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity);
+ var circle = new CircleF(new Point2(-2, 1), .99f);
+
+ Assert.False(rectangle.Intersects(circle));
+ }
+
+ [Fact]
+ public void Rectangle_rotated_45_degrees_does_not_intersect_circle_in_bottom_right()
+ {
+ /*
+ * :
+ * :
+ * +-.
+ * .........../ / ........
+ * +./* *
+ * * *
+ * :* *
+ */
+ IShapeF rectangle = new OrientedRectangle(new Point2(-1, 1), new Size2(1.42f, 1.42f), Matrix2.CreateRotationZ(-MathHelper.PiOver4));
+ var circle = new CircleF(new Point2(1, -1), 1.4f);
+
+ Assert.False(rectangle.Intersects(circle));
+ }
+
+ [Fact]
+ public void Axis_aligned_rectangle_does_not_intersect_rectangle()
+ {
+ /*
+ * :
+ * :
+ * +-+
+ * ..........| |**.......
+ * +-+ *
+ * :**
+ * :
+ */
+ IShapeF rectangle = new OrientedRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity);
+ var rect = new RectangleF(new Point2(0.001f, 0), new Size2(2, 2));
+
+ Assert.False(rectangle.Intersects(rect));
+ }
+
+ [Fact]
+ public void Axis_aligned_rectangle_intersects_rectangle()
+ {
+ /*
+ * :
+ * :
+ * +-+
+ * ..........| |**.......
+ * +-+ *
+ * :**
+ * :
+ */
+ IShapeF rectangle = new OrientedRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity);
+ var rect = new RectangleF(new Point2(0, 0), new Size2(2, 2));
+
+ Assert.True(rectangle.Intersects(rect));
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Size2Tests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Size2Tests.cs
new file mode 100644
index 0000000..faaa426
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Primitives/Size2Tests.cs
@@ -0,0 +1,304 @@
+//using System.Collections.Generic;
+//using System.Globalization;
+//using Microsoft.Xna.Framework;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Primitives
+//{
+//
+// public class Size2Tests
+// {
+// public IEnumerable<TestCaseData> ConstructorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(0, 0).SetName(
+// "The empty size has the expected dimensions.");
+// yield return
+// new TestCaseData(float.MinValue, float.MaxValue).SetName
+// (
+// "A non-empty size has the expected dimensions.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ConstructorTestCases))]
+// public void Constructor(float width, float height)
+// {
+// var size = new Size2(width, height);
+// Assert.Equal(width, size.Width);
+// Assert.Equal(height, size.Height);
+// }
+
+// public IEnumerable<TestCaseData> DimensionsTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), 0, 0).SetName(
+// "The empty size has the expected dimensions.");
+// yield return
+// new TestCaseData(new Size2(float.MinValue, float.MaxValue), float.MinValue, float.MaxValue).SetName
+// (
+// "A non-empty size has the expected dimensions.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(DimensionsTestCases))]
+// public void Dimensions(Size2 size, float expectedWidth, float expecetedHeight)
+// {
+// Assert.Equal(expectedWidth, size.Width);
+// Assert.Equal(expecetedHeight, size.Height);
+
+// size.Width = 10;
+// Assert.Equal(10, size.Width);
+
+// size.Height = -10.123f;
+// Assert.Equal(-10.123f, size.Height);
+// }
+
+// public IEnumerable<TestCaseData> AdditionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Size2(), new Size2()).SetName(
+// "The addition of two empty sizes is the empty size.");
+// yield return
+// new TestCaseData(new Size2(5, 5), new Size2(15, 15), new Size2(20, 20)).SetName(
+// "The addition of two non-empty sizes is the expected size.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(AdditionTestCases))]
+// public void Addition(Size2 size1, Size2 size2, Size2 expectedSize)
+// {
+// Assert.Equal(expectedSize, size1 + size2);
+// Assert.Equal(expectedSize, Size2.Add(size1, size2));
+// }
+
+// public IEnumerable<TestCaseData> SubtractionTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Size2(), new Size2()).SetName(
+// "The subtraction of two empty sizes is the empty size.");
+// yield return
+// new TestCaseData(new Size2(5, 5), new Size2(15, 15), new Size2(-10, -10)).SetName(
+// "The subtraction of two non-empty sizes is the expected size.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(SubtractionTestCases))]
+// public void Subtraction(Size2 size1, Size2 size2, Size2 expectedSize)
+// {
+// Assert.Equal(expectedSize, size1 - size2);
+// Assert.Equal(expectedSize, Size2.Subtract(size1, size2));
+// }
+
+// public IEnumerable<TestCaseData> EqualityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Size2(), true).SetName("Two empty sizes are equal.");
+// yield return
+// new TestCaseData(new Size2(float.MinValue, float.MaxValue),
+// new Size2(float.MaxValue, float.MinValue), false).SetName(
+// "Two different non-empty sizes are not equal.");
+// yield return
+// new TestCaseData(
+// new Size2(float.MinValue, float.MaxValue), new Size2(float.MinValue, float.MaxValue), true)
+// .SetName(
+// "Two identical non-empty sizes are equal.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(EqualityTestCases))]
+// public void Equality(Size2 size1, Size2 size2, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(Equals(size1, size2) == expectedToBeEqual);
+// Assert.IsTrue(size1 == size2 == expectedToBeEqual);
+// Assert.IsFalse(size1 == size2 != expectedToBeEqual);
+// Assert.IsTrue(size1.Equals(size2) == expectedToBeEqual);
+
+// if (expectedToBeEqual)
+// Assert.Equal(size1.GetHashCode(), size2.GetHashCode());
+// }
+
+// public IEnumerable<TestCaseData> InequalityTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), null, false).SetName("A size is not equal to a null object.");
+// yield return
+// new TestCaseData(new Size2(), new object(), false).SetName(
+// "A size is not equal to an instantiated object.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(InequalityTestCases))]
+// public void Inequality(Size2 size, object obj, bool expectedToBeEqual)
+// {
+// Assert.IsTrue(size.Equals(obj) == expectedToBeEqual);
+// }
+
+// public IEnumerable<TestCaseData> HashCodeTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Size2(), true).SetName(
+// "Two empty sizes have the same hash code.");
+// yield return
+// new TestCaseData(new Size2(50, 50), new Size2(50, 50), true).SetName(
+// "Two indentical non-empty sizes have the same hash code.");
+// yield return
+// new TestCaseData(new Size2(0, 0), new Size2(50, 50), false).SetName(
+// "Two different non-empty sizes do not have the same hash code.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(HashCodeTestCases))]
+// public void HashCode(Size2 size1, Size2 size2, bool expectedThatHashCodesAreEqual)
+// {
+// var hashCode1 = size1.GetHashCode();
+// var hashCode2 = size2.GetHashCode();
+// if (expectedThatHashCodesAreEqual)
+// Assert.Equal(hashCode1, hashCode2);
+// else
+// Assert.AreNotEqual(hashCode1, hashCode2);
+// }
+
+// public IEnumerable<TestCaseData> ToPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Point2()).SetName("The empty size converted to a point is the zero point.");
+// yield return
+// new TestCaseData(new Size2(float.MinValue, float.MaxValue), new Point2(float.MinValue, float.MaxValue)).SetName(
+// "A non-empty size converted to a point is the expected point.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToPointTestCases))]
+// public void ToPoint(Size2 size, Point2 expectedPoint)
+// {
+// var actualPoint = (Point2)size;
+// Assert.Equal(expectedPoint, actualPoint);
+// }
+
+// public IEnumerable<TestCaseData> FromPointTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Point2(), new Size2()).SetName("The zero point converted to a size is the empty size.");
+// yield return
+// new TestCaseData(new Point2(float.MinValue, float.MaxValue), new Size2(float.MinValue, float.MaxValue)).SetName(
+// "A non-zero point converted to a size is the expected size.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromPointTestCases))]
+// public void FromPoint(Point2 point, Size2 expectedSize)
+// {
+// var actualSize = (Size2)point;
+// Assert.Equal(expectedSize, actualSize);
+// }
+
+// public IEnumerable<TestCaseData> ToVectorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(), new Vector2()).SetName("The empty size converted to a vector is the zero vector.");
+// yield return
+// new TestCaseData(new Size2(float.MinValue, float.MaxValue), new Vector2(float.MinValue, float.MaxValue)).SetName(
+// "A non-empty size converted to a vector is the expected vector.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(ToVectorTestCases))]
+// public void ToVector(Size2 size, Vector2 expectedVector)
+// {
+// var actualVector = (Vector2)size;
+// Assert.Equal(expectedVector, actualVector);
+// }
+
+// public IEnumerable<TestCaseData> FromVectorTestCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Vector2(), new Size2()).SetName("The zero vector converted to a size is the empty size.");
+// yield return
+// new TestCaseData(new Vector2(float.MinValue, float.MaxValue), new Size2(float.MinValue, float.MaxValue)).SetName(
+// "A non-zero vector converted to a size is the expected size.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(FromVectorTestCases))]
+// public void FromVector(Vector2 vector, Size2 expectedSize)
+// {
+// var actualSize = (Size2)vector;
+// Assert.Equal(expectedSize, actualSize);
+// }
+
+// //public IEnumerable<TestCaseData> FromSizeTestCases
+// //{
+// // get
+// // {
+// // yield return
+// // new TestCaseData(new Size2(), new Size2()).SetName("The empty size converted to a size is the empty size.");
+// // yield return
+// // new TestCaseData(new Size2(int.MinValue, int.MaxValue), new Size2(int.MinValue, int.MaxValue)).SetName(
+// // "A non-zero size converted to a size is the expected size.");
+// // }
+// //}
+
+// //[Fact]
+// //[TestCaseSource(nameof(FromSizeTestCases))]
+// //public void FromSize(Size2 size, Size2 expectedSize)
+// //{
+// // var actualSize = (Size2)size;
+// // Assert.Equal(expectedSize, actualSize);
+// //}
+
+// public IEnumerable<TestCaseData> StringCases
+// {
+// get
+// {
+// yield return
+// new TestCaseData(new Size2(),
+// string.Format(CultureInfo.CurrentCulture, "Width: {0}, Height: {1}", 0, 0)).SetName(
+// "The empty size has the expected string representation using the current culture.");
+// yield return new TestCaseData(new Size2(5.1f, -5.123f),
+// string.Format(CultureInfo.CurrentCulture, "Width: {0}, Height: {1}", 5.1f, -5.123f)).SetName(
+// "A non-empty size has the expected string representation using the current culture.");
+// }
+// }
+
+// [Fact]
+// [TestCaseSource(nameof(StringCases))]
+// public void String(Size2 size, string expectedString)
+// {
+// var actualString = size.ToString();
+// Assert.Equal(expectedString, actualString);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/RangeTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/RangeTests.cs
new file mode 100644
index 0000000..d022dcc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/RangeTests.cs
@@ -0,0 +1,102 @@
+using System;
+using Xunit;
+
+namespace MonoGame.Extended.Tests
+{
+
+ public class RangeTests
+ {
+ [Fact]
+ public void ConstructorTest()
+ {
+ //can pass min < max
+ var unused = new Range<int>(10, 100);
+ //can't pass min > max
+ Assert.Throws<ArgumentException>(() => new Range<int>(100, 10));
+ //can pass min == max
+ var unused1 = new Range<int>(10, 10);
+ }
+
+ [Fact]
+ public void DegenerateTest()
+ {
+ var proper = new Range<double>(0, 1);
+ Assert.True(proper.IsProper);
+ Assert.False(proper.IsDegenerate);
+
+ var degenerate = new Range<double>(1, 1);
+ Assert.False(degenerate.IsProper);
+ Assert.True(degenerate.IsDegenerate);
+ }
+
+ [Fact]
+ public void IntegerTest()
+ {
+ var range = new Range<int>(10, 100);
+
+ Assert.Equal(10, range.Min);
+ Assert.Equal(100, range.Max);
+
+ for (var i = 10; i <= 100; i++)
+ {
+ Assert.True(range.IsInBetween(i));
+ }
+
+ Assert.False(range.IsInBetween(9));
+ Assert.False(range.IsInBetween(101));
+ Assert.False(range.IsInBetween(10, true));
+ Assert.False(range.IsInBetween(100, maxValueExclusive: true));
+ }
+
+ [Fact]
+ public void FloatTest()
+ {
+ var range = new Range<float>(0f, 1f);
+
+ Assert.Equal(0f, range.Min);
+ Assert.Equal(1f, range.Max);
+
+ for (float i = 0; i <= 1f; i += 0.001f)
+ {
+ Assert.True(range.IsInBetween(i));
+ }
+
+ Assert.False(range.IsInBetween(-float.Epsilon));
+ Assert.False(range.IsInBetween(1.00001f));
+
+ Assert.False(range.IsInBetween(0f, true));
+ Assert.False(range.IsInBetween(1f, maxValueExclusive: true));
+ }
+
+ [Fact]
+ public void OperatorTest()
+ {
+ var rangeA = new Range<int>(0, 1);
+ var rangeB = new Range<int>(0, 1);
+ var rangeC = new Range<int>(1, 2);
+ var rangeD = new Range<double>(0, 1);
+
+ Assert.True(rangeA == rangeB);
+ Assert.False(rangeA == rangeC);
+
+ Assert.False(rangeA != rangeB);
+ Assert.True(rangeA != rangeC);
+
+ Assert.True(rangeA.Equals(rangeB));
+ Assert.False(rangeA.Equals(rangeC));
+ Assert.False(rangeA.Equals(rangeD));
+
+ Range<int> implict = 1;
+ Assert.Equal(1, implict.Max);
+ Assert.Equal(1, implict.Min);
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var range = new Range<float>(0, 1);
+
+ Assert.Equal("Range<Single> [0 1]", range.ToString());
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs
new file mode 100644
index 0000000..ae8e1e7
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/ColorJsonConverterTests.cs
@@ -0,0 +1,66 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Serialization;
+
+namespace MonoGame.Extended.Tests.Serialization;
+
+public sealed class ColorJsonConverterTests
+{
+ private readonly ColorJsonConverter _converter = new ColorJsonConverter();
+
+ [Fact]
+ public void CanConvert_ColorType_ReturnsTrue()
+ {
+ var colorType = typeof(Color);
+ var result = _converter.CanConvert(colorType);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void CanConvert_NonColorType_ReturnsFalse()
+ {
+ var otherType = typeof(string);
+ var result = _converter.CanConvert(otherType);
+ Assert.False(result);
+ }
+
+ [Theory]
+ [InlineData("Red", 255, 0, 0, 255)]
+ [InlineData("#FF0000FF", 255, 0, 0, 255)]
+ public void Read_ValidColorJson_ReturnsExpectedColor(string jsonValue, byte r, byte g, byte b, byte a)
+ {
+ var json = $"\"{jsonValue}\"";
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+
+ reader.Read();
+ var actual = _converter.Read(ref reader, typeof(Color), new JsonSerializerOptions());
+
+ var expected = new Color(r, g, b, a);
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Write_ValidColor_WritesExpectedJson()
+ {
+ var expected = "#ff000000";
+ var color = ColorHelper.FromHex(expected);
+ using var stream = new MemoryStream();
+ using var writer = new Utf8JsonWriter(stream);
+
+ _converter.Write(writer, color, new JsonSerializerOptions());
+ writer.Flush();
+ var actual = Encoding.UTF8.GetString(stream.ToArray());
+
+ Assert.Equal($"\"{expected}\"", actual);
+ }
+
+ [Fact]
+ public void Write_NullWrier_ThrowArgumentNullException()
+ {
+ var color = Color.MonoGameOrange;
+ Assert.Throws<ArgumentNullException>(() => _converter.Write(null, color, new JsonSerializerOptions()));
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs
new file mode 100644
index 0000000..96f9c4f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Serialization/RectangleFJsonConverterTest.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using System.Text.Json;
+using MonoGame.Extended.Serialization;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Serialization;
+
+public class RectangleFJsonConverterTest
+{
+
+ public class TestContent
+ {
+ public RectangleF Box { get; set; }
+ }
+
+ [Fact]
+ public void ConstructorTest()
+ {
+ var jsonData = @"
+{
+ ""box"": ""1 1 10 10""
+}
+";
+ var options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+ options.Converters.Add(new RectangleFJsonConverter());
+ var content = JsonSerializer.Deserialize<TestContent>(jsonData, options);
+
+ Assert.Equal(1, content.Box.Left);
+ Assert.Equal(1, content.Box.Top);
+ Assert.Equal(10, content.Box.Width);
+ Assert.Equal(10, content.Box.Height);
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Shapes/PolygonFTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Shapes/PolygonFTests.cs
new file mode 100644
index 0000000..d052908
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Shapes/PolygonFTests.cs
@@ -0,0 +1,87 @@
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Shapes;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Shapes
+{
+ public class PolygonFTests
+ {
+ [Fact]
+ public void Polygon_Contains_Point_Test()
+ {
+ var vertices = new[]
+ {
+ new Vector2(0, 0),
+ new Vector2(10, 0),
+ new Vector2(10, 10),
+ new Vector2(0, 10)
+ };
+
+ var polygon = new Polygon(vertices);
+
+ Assert.True(polygon.Contains(new Vector2(5, 5)));
+ Assert.True(polygon.Contains(new Vector2(0.01f, 0.01f)));
+ Assert.True(polygon.Contains(new Vector2(9.99f, 9.99f)));
+ Assert.False(polygon.Contains(new Vector2(-1f, -1f)));
+ Assert.False(polygon.Contains(new Vector2(-11f, -11f)));
+ }
+
+ [Fact]
+ public void Polygon_Transform_Translation_Test()
+ {
+ var vertices = new[]
+ {
+ new Vector2(0, 0),
+ new Vector2(10, 0),
+ new Vector2(10, 10),
+ new Vector2(0, 10)
+ };
+
+ var polygon = new Polygon(vertices);
+ polygon.Offset(new Vector2(2, 3));
+
+ Assert.Equal(new Vector2(2, 3), polygon.Vertices[0]);
+ Assert.Equal(new Vector2(12, 3), polygon.Vertices[1]);
+ Assert.Equal(new Vector2(12, 13), polygon.Vertices[2]);
+ Assert.Equal(new Vector2(2, 13), polygon.Vertices[3]);
+ }
+
+ [Fact]
+ public void Polygon_Transform_Rotation_Test()
+ {
+ var vertices = new[]
+ {
+ new Vector2(-5, -5),
+ new Vector2(5, 10),
+ new Vector2(-5, 10)
+ };
+
+ var polygon = new Polygon(vertices);
+ polygon.Rotate(MathHelper.ToRadians(90));
+
+ const float tolerance = 0.01f;
+ Assert.True(new Vector2(5, -5).EqualsWithTolerence(polygon.Vertices[0], tolerance));
+ Assert.True(new Vector2(-10, 5).EqualsWithTolerence(polygon.Vertices[1], tolerance));
+ Assert.True(new Vector2(-10, -5).EqualsWithTolerence(polygon.Vertices[2], tolerance));
+ }
+
+ [Fact]
+ public void Polygon_Transform_Scale_Test()
+ {
+ var vertices = new[]
+ {
+ new Vector2(0, -1),
+ new Vector2(1, 1),
+ new Vector2(-1, 1)
+ };
+
+ var polygon = new Polygon(vertices);
+ polygon.Scale(new Vector2(1, -0.5f));
+
+ const float tolerance = 0.01f;
+ Assert.True(new Vector2(0, -0.5f).EqualsWithTolerence(polygon.Vertices[0], tolerance), "0");
+ Assert.True(new Vector2(2f, 0.5f).EqualsWithTolerence(polygon.Vertices[1], tolerance), "1");
+ Assert.True(new Vector2(-2f, 0.5f).EqualsWithTolerence(polygon.Vertices[2], tolerance), "2");
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteSheetAnimationTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteSheetAnimationTests.cs
new file mode 100644
index 0000000..35ad3e3
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteSheetAnimationTests.cs
@@ -0,0 +1,910 @@
+using System;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Sprites;
+using MonoGame.Extended.TextureAtlases;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.Sprites
+{
+ public class SpriteSheetAnimationTests
+ {
+ [Theory]
+ [InlineData(0, 0)]
+ [InlineData(0, 0.9f)]
+ [InlineData(1, 1f)]
+ [InlineData(1, 1.9f)]
+ [InlineData(0, 2f)]
+ [InlineData(0, 2.9f)]
+ [InlineData(1, 3f)]
+ [InlineData(0, 4f)]
+ [InlineData(1, 5f)]
+ public void Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Fact]
+ public void Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(0, 0.9f)]
+ [InlineData(1, 1f)]
+ [InlineData(1, 1.1f)]
+ [InlineData(1, 1.9f)]
+ public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(1, 2f)]
+ [InlineData(1, 3f)]
+ [InlineData(1, 4f)]
+ public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+ }
+
+ [Fact]
+ public void Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+
+ isCompleteFired = false; // Reset isCompleteFired for next execution
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
+ }
+
+ [Theory]
+ [InlineData(1, 0)]
+ [InlineData(1, 0.9f)]
+ [InlineData(0, 1f)]
+ [InlineData(0, 1.9f)]
+ [InlineData(1, 2f)]
+ [InlineData(1, 2.9f)]
+ [InlineData(0, 3f)]
+ [InlineData(1, 4f)]
+ [InlineData(0, 5f)]
+ public void Reversed_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Fact]
+ public void Reversed_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(1, 0.9f)]
+ [InlineData(0, 1f)]
+ [InlineData(0, 1.1f)]
+ [InlineData(0, 1.9f)]
+ public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(0, 2f)]
+ [InlineData(0, 3f)]
+ [InlineData(0, 4f)]
+ public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+ }
+
+ [Fact]
+ public void Reversed_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+
+ isCompleteFired = false; // Reset isCompleteFired for next execution
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired); // Event is not fired again as animation was already completed;
+ }
+
+ [Theory]
+ [InlineData(0, 0)]
+ [InlineData(0, 0.9f)]
+ [InlineData(1, 1f)]
+ [InlineData(1, 1.9f)]
+ [InlineData(0, 2f)]
+ [InlineData(0, 2.9f)]
+ [InlineData(1, 3f)]
+ [InlineData(0, 4f)]
+ [InlineData(1, 5f)]
+ [InlineData(0, 6f)]
+ [InlineData(1, 7f)]
+ [InlineData(0, 8f)]
+ public void PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Fact]
+ public void PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(0, 0.9f)]
+ [InlineData(1, 1f)]
+ [InlineData(1, 1.9f)]
+ [InlineData(0, 2f)]
+ [InlineData(0, 2.9f)]
+ public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(0, 3f)]
+ [InlineData(0, 4f)]
+ [InlineData(0, 5f)]
+ public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+ }
+
+ [Fact]
+ public void PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+ var textureRegion2D3 = new TextureRegion2D("Region 3", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2, textureRegion2D3 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1, 2 },
+ 1,
+ false,
+ false,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+
+ isCompleteFired = false; // Reset isCompleteFired for next execution
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
+ }
+
+ [Theory]
+ [InlineData(1, 0)]
+ [InlineData(1, 0.9f)]
+ [InlineData(0, 1f)]
+ [InlineData(0, 1.9f)]
+ [InlineData(1, 2f)]
+ [InlineData(1, 2.9f)]
+ [InlineData(0, 3f)]
+ [InlineData(1, 4f)]
+ [InlineData(0, 5f)]
+ [InlineData(1, 6f)]
+ [InlineData(0, 7f)]
+ [InlineData(1, 8f)]
+ public void Reversed_PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Fact]
+ public void Reversed_PingPong_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ true,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(1, 0.9f)]
+ [InlineData(0, 1f)]
+ [InlineData(0, 1.9f)]
+ [InlineData(1, 2f)]
+ [InlineData(1, 2.9f)]
+ public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Not_Complete(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+ }
+
+ [Theory]
+ [InlineData(1, 3f)]
+ [InlineData(1, 4f)]
+ [InlineData(1, 5f)]
+ public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached(int expectedTextureRegionIndex, float time)
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1 },
+ 1,
+ false,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(time));
+
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[expectedTextureRegionIndex], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+ }
+
+ [Fact]
+ public void Reversed_PingPong_Non_Looping_SpriteSheetAnimation_Should_Return_Correct_Frame_And_Complete_When_AnimationDuration_Is_Reached_Over_Multiple_Updates()
+ {
+ var textureRegion2D1 = new TextureRegion2D("Region 1", null, new Rectangle());
+ var textureRegion2D2 = new TextureRegion2D("Region 2", null, new Rectangle());
+ var textureRegion2D3 = new TextureRegion2D("Region 3", null, new Rectangle());
+
+ var textureRegions = new[] { textureRegion2D1, textureRegion2D2, textureRegion2D3 };
+
+ var spriteSheetAnimationData = new SpriteSheetAnimationData(
+ new[] { 0, 1, 2 },
+ 1,
+ false,
+ true,
+ true
+ );
+
+ var spriteSheetAnimation = new SpriteSheetAnimation("Test", textureRegions, spriteSheetAnimationData);
+
+ var isCompleteFired = false;
+ spriteSheetAnimation.OnCompleted += () => isCompleteFired = true;
+
+ spriteSheetAnimation.Play();
+
+ var gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(0));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[0], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[1], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
+ Assert.False(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired);
+
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.True(isCompleteFired);
+
+ isCompleteFired = false; // Reset isCompleteFired for next execution
+ gameTime = new GameTime(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ spriteSheetAnimation.Update(gameTime);
+
+ Assert.Equal(textureRegions[2], spriteSheetAnimation.CurrentFrame);
+ Assert.True(spriteSheetAnimation.IsComplete);
+ Assert.False(isCompleteFired); // Event is not fired again as animation was already completed
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteTests.cs
new file mode 100644
index 0000000..307e272
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Sprites/SpriteTests.cs
@@ -0,0 +1,91 @@
+//using Microsoft.Xna.Framework;
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.Sprites;
+//using MonoGame.Extended.TextureAtlases;
+//using NSubstitute;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Sprites
+//{
+//
+// public class SpriteTests
+// {
+// [Fact]
+// public void Sprite_BoundingRectangleAfterPosition_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 50, 200);
+// var sprite = new Sprite(texture);
+
+// Assert.Equal(new RectangleF(375, 140, 50, 200), sprite.GetBoundingRectangle(new Vector2(400, 240), 0, Vector2.One));
+// }
+
+// [Fact]
+// public void Sprite_BoundingRectangleAfterOrigin_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 50, 200);
+// var sprite = new Sprite(texture) { OriginNormalized = new Vector2(1.0f, 1.0f) };
+
+// Assert.Equal(new RectangleF(-50, -200, 50, 200), sprite.GetBoundingRectangle(Vector2.Zero, 0, Vector2.One));
+// }
+
+// [Fact]
+// public void Sprite_BoundingRectangleAfterScale_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 50, 200);
+// var sprite = new Sprite(texture);
+
+// Assert.Equal(new RectangleF(-50, -200, 100, 400), sprite.GetBoundingRectangle(Vector2.Zero, 0, Vector2.One * 2.0f));
+// }
+
+// [Fact]
+// public void Sprite_BoundingRectangleAfterRotation_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 50, 200);
+// var sprite = new Sprite(texture);
+
+// AssertExtensions.AreApproximatelyEqual(new RectangleF(-100, -25, 200, 50), sprite.GetBoundingRectangle(Vector2.Zero, MathHelper.ToRadians(90), Vector2.One * 2.0f));
+// }
+
+// [Fact]
+// public void Sprite_TextureRegionIsFullTextureWhenTextureConstructorIsUsed_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 100, 200);
+// var sprite = new Sprite(texture);
+
+// Assert.Equal(new Rectangle(0, 0, 100, 200), sprite.TextureRegion.Bounds);
+// }
+
+// [Fact]
+// public void Sprite_DefaultOriginIsCentre_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 100, 200);
+// var sprite = new Sprite(texture);
+
+// Assert.Equal(new Vector2(0.5f, 0.5f), sprite.OriginNormalized);
+// Assert.Equal(new Vector2(50, 100), sprite.Origin);
+// }
+
+// [Fact]
+// public void Sprite_PreserveNormalizedOriginWhenTextureRegionChanges_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(graphicsDevice, 100, 100);
+// var textureRegion = new TextureRegion2D(texture, 10, 20, 30, 40);
+// var sprite = new Sprite(textureRegion);
+
+// Assert.Equal(new Vector2(0.5f, 0.5f), sprite.OriginNormalized);
+// Assert.Equal(new Vector2(15, 20), sprite.Origin);
+
+// sprite.TextureRegion = new TextureRegion2D(texture, 30, 40, 50, 60);
+
+// Assert.Equal(new Vector2(0.5f, 0.5f), sprite.OriginNormalized);
+// Assert.Equal(new Vector2(25, 30), sprite.Origin);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGame.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGame.cs
new file mode 100644
index 0000000..dad6e8a
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGame.cs
@@ -0,0 +1,21 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+ public class TestGame : Game
+ {
+ private readonly GraphicsDeviceManager _graphicsDeviceManager;
+
+ public TestGame()
+ {
+ _graphicsDeviceManager = new GraphicsDeviceManager(this);
+ RunOneFrame();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _graphicsDeviceManager.Dispose();
+ base.Dispose(disposing);
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGraphicsDevice.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGraphicsDevice.cs
new file mode 100644
index 0000000..8a8741b
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestGraphicsDevice.cs
@@ -0,0 +1,12 @@
+using Microsoft.Xna.Framework.Graphics;
+
+namespace MonoGame.Extended.Tests
+{
+ public class TestGraphicsDevice : GraphicsDevice
+ {
+ public TestGraphicsDevice()
+ : base(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, new PresentationParameters())
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestHelper.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestHelper.cs
new file mode 100644
index 0000000..b6a02bb
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TestHelper.cs
@@ -0,0 +1,27 @@
+//using Microsoft.Xna.Framework;
+//using Microsoft.Xna.Framework.Graphics;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests
+//{
+// public static class TestHelper
+// {
+// public static void AreEqual(Vector3 a, Vector3 b, double delta)
+// {
+// Assert.Equal(a.X, b.X, delta);
+// Assert.Equal(a.Y, b.Y, delta);
+// Assert.Equal(a.Z, b.Z, delta);
+// }
+
+// public static GraphicsDevice CreateGraphicsDevice()
+// {
+// return new GraphicsDevice(
+// GraphicsAdapter.DefaultAdapter,
+// GraphicsProfile.HiDef,
+// new PresentationParameters())
+// {
+// Viewport = new Viewport(0, 0, 800, 480)
+// };
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureAtlasTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureAtlasTests.cs
new file mode 100644
index 0000000..13d9dfc
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureAtlasTests.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+using Xunit;
+
+namespace MonoGame.Extended.Tests.TextureAtlases
+{
+ //public class TextureAtlasTests : IDisposable
+ //{
+ // private readonly TestGame _game;
+
+ // public TextureAtlasTests()
+ // {
+ // _game = new TestGame();
+ // }
+
+ // public void Dispose()
+ // {
+ // _game.Dispose();
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_CreateRegion_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // var region = atlas.CreateRegion("region0", 10, 20, 30, 40);
+
+ // Assert.Same(texture, region.Texture);
+ // Assert.Equal(10, region.X);
+ // Assert.Equal(20, region.Y);
+ // Assert.Equal(30, region.Width);
+ // Assert.Equal(40, region.Height);
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_GetRegionsByIndex_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // var region0 = atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // var region1 = atlas.CreateRegion("region1", 50, 60, 35, 45);
+
+ // Assert.Same(region0, atlas[0]);
+ // Assert.Same(region1, atlas[1]);
+ // Assert.Same(region0, atlas.GetRegion(0));
+ // Assert.Same(region1, atlas.GetRegion(1));
+ // }
+ // }
+
+
+ // [Fact]
+ // public void TextureAtlas_GetRegionsByName_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // var region0 = atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // var region1 = atlas.CreateRegion("region1", 50, 60, 35, 45);
+
+ // Assert.Same(region0, atlas["region0"]);
+ // Assert.Same(region1, atlas["region1"]);
+ // Assert.Same(region0, atlas.GetRegion("region0"));
+ // Assert.Same(region1, atlas.GetRegion("region1"));
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_RemoveRegions_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // var region0 = atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // var region1 = atlas.CreateRegion("region1", 50, 60, 35, 45);
+ // var region2 = atlas.CreateRegion("region2", 32, 33, 34, 35);
+
+ // Assert.Same(texture, atlas.Texture);
+ // Assert.Equal(3, atlas.RegionCount);
+ // Assert.Equal(atlas.RegionCount, atlas.Regions.Count());
+ // Assert.Same(region1, atlas[1]);
+
+ // atlas.RemoveRegion(1);
+
+ // Assert.Equal(2, atlas.Regions.Count());
+ // Assert.Same(region0, atlas[0]);
+ // Assert.Same(region2, atlas[1]);
+
+ // atlas.RemoveRegion("region0");
+
+ // Assert.Single(atlas.Regions);
+ // Assert.Same(region2, atlas[0]);
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_CreateRegionThatAlreadyExistsThrowsException_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // Assert.Throws<InvalidOperationException>(() => atlas.CreateRegion("region0", 50, 60, 35, 45));
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_GetRegion_InvalidIndexThrowsException_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // Assert.Throws<IndexOutOfRangeException>(() => atlas.GetRegion(-1));
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_GetRegion_InvalidNameThrowsException_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // Assert.Throws<KeyNotFoundException>(() => atlas.GetRegion("region1"));
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_EnumerateRegions_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 100, 200))
+ // {
+ // var atlas = new TextureAtlas(null, texture);
+
+ // var regions = new TextureRegion2D[3];
+ // regions[0] = atlas.CreateRegion("region0", 10, 20, 30, 40);
+ // regions[1] = atlas.CreateRegion("region1", 50, 60, 35, 45);
+ // regions[2] = atlas.CreateRegion("region2", 32, 33, 34, 35);
+ // var index = 0;
+
+ // foreach (var region in atlas)
+ // {
+ // Assert.Same(region, regions[index]);
+ // index++;
+ // }
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_Create_WithDefaultParameters_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 50, 100) {Name = "testTexture"})
+ // {
+ // var atlas = TextureAtlas.Create(null, texture, 25, 50);
+
+ // Assert.Equal(4, atlas.RegionCount);
+ // Assert.True(atlas.Regions.All(i => i.Width == 25));
+ // Assert.True(atlas.Regions.All(i => i.Height == 50));
+ // Assert.True(atlas.Regions.All(i => ReferenceEquals(i.Texture, texture)));
+ // Assert.True(atlas.Regions.All(i => i.Name.StartsWith(texture.Name)));
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_Create_WithMaxRegionCount_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 64, 64))
+ // {
+ // var atlas = TextureAtlas.Create(null, texture, 32, 32, maxRegionCount: 3);
+
+ // Assert.Equal(3, atlas.RegionCount);
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_Create_WithMargin_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 24, 24))
+ // {
+ // var atlas = TextureAtlas.Create(null, texture, 10, 10, margin: 2);
+
+ // Assert.Equal(4, atlas.RegionCount);
+ // Assert.True(atlas.Regions.All(i => i.Width == 10 && i.Height == 10));
+ // Assert.Equal(2, atlas[0].X);
+ // Assert.Equal(2, atlas[0].Y);
+ // Assert.Equal(12, atlas[3].X);
+ // Assert.Equal(12, atlas[3].Y);
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_Create_WithSpacing_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 24, 24))
+ // {
+ // var atlas = TextureAtlas.Create(null, texture, 10, 10, spacing: 2);
+
+ // Assert.Equal(4, atlas.RegionCount);
+ // Assert.True(atlas.Regions.All(i => i.Width == 10 && i.Height == 10));
+ // Assert.Equal(0, atlas[0].X);
+ // Assert.Equal(0, atlas[0].Y);
+ // Assert.Equal(12, atlas[3].X);
+ // Assert.Equal(12, atlas[3].Y);
+ // }
+ // }
+
+ // [Fact]
+ // public void TextureAtlas_Create_WithMarginAndSpacing_Test()
+ // {
+ // using (var texture = new Texture2D(_game.GraphicsDevice, 28, 28))
+ // {
+ // var atlas = TextureAtlas.Create(null, texture, 10, 10, margin: 3, spacing: 2);
+
+ // Assert.Equal(4, atlas.RegionCount);
+ // Assert.True(atlas.Regions.All(i => i.Width == 10 && i.Height == 10));
+ // Assert.Equal(3, atlas[0].X);
+ // Assert.Equal(3, atlas[0].Y);
+ // Assert.Equal(15, atlas[3].X);
+ // Assert.Equal(15, atlas[3].Y);
+ // }
+ // }
+ //}
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureRegion2DTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureRegion2DTests.cs
new file mode 100644
index 0000000..1274b10
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/TextureAtlases/TextureRegion2DTests.cs
@@ -0,0 +1,40 @@
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.TextureAtlases;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.TextureAtlases
+//{
+//
+// public class TextureRegion2DTests
+// {
+// [Fact]
+// public void TextureRegion2D_FromTexture_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 100, 200);
+// var textureRegion = new TextureRegion2D(texture);
+
+// Assert.AreSame(texture, textureRegion.Texture);
+// Assert.Equal(0, textureRegion.X);
+// Assert.Equal(0, textureRegion.Y);
+// Assert.Equal(100, textureRegion.Width);
+// Assert.Equal(200, textureRegion.Height);
+// Assert.IsNull(textureRegion.Tag);
+// }
+
+// [Fact]
+// public void TextureRegion2D_Specified_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 100, 200);
+// var textureRegion = new TextureRegion2D(texture, 10, 20, 30, 40);
+
+// Assert.AreSame(texture, textureRegion.Texture);
+// Assert.Equal(10, textureRegion.X);
+// Assert.Equal(20, textureRegion.Y);
+// Assert.Equal(30, textureRegion.Width);
+// Assert.Equal(40, textureRegion.Height);
+// Assert.IsNull(textureRegion.Tag);
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Vector2ExtensionsTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Vector2ExtensionsTests.cs
new file mode 100644
index 0000000..aa1d889
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/Vector2ExtensionsTests.cs
@@ -0,0 +1,101 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tests
+{
+ public class Vector2ExtensionsTests
+ {
+ [Fact]
+ public void Vector2_EqualsWithTolerence_Test()
+ {
+ var a = new Vector2(1f, 1f);
+ var b = new Vector2(1.0000001f, 1.0000001f);
+
+ Assert.False(a.Equals(b));
+ Assert.True(a.EqualsWithTolerence(b));
+ }
+
+ [Fact]
+ public void Vector2_NormalizedCopy_Test()
+ {
+ var a = new Vector2(5, -10);
+ var b = a.NormalizedCopy();
+
+ Assert.True(new Vector2(0.4472136f, -0.8944272f).EqualsWithTolerence(b));
+ }
+
+ [Fact]
+ public void Vector2_Perpendicular_Test()
+ {
+ // http://mathworld.wolfram.com/PerpendicularVector.html
+ var a = new Vector2(5, -10);
+ var b = a.PerpendicularClockwise();
+ var c = a.PerpendicularCounterClockwise();
+
+ Assert.Equal(new Vector2(-10, -5), b);
+ Assert.Equal(new Vector2(10, 5), c);
+ }
+
+ [Fact]
+ public void Vector2_Rotate_90_Degrees_Test()
+ {
+ var a = new Vector2(0, -10);
+ var b = a.Rotate(MathHelper.ToRadians(90));
+
+ Assert.True(new Vector2(10, 0).EqualsWithTolerence(b));
+ }
+
+ [Fact]
+ public void Vector2_Rotate_360_Degrees_Test()
+ {
+ var a = new Vector2(0, 10);
+ var b = a.Rotate(MathHelper.ToRadians(360));
+
+ Assert.True(new Vector2(0, 10).EqualsWithTolerence(b));
+ }
+
+ [Fact]
+ public void Vector2_Rotate_45_Degrees_Test()
+ {
+ var a = new Vector2(0, -10);
+ var b = a.Rotate(MathHelper.ToRadians(45));
+
+ Assert.True(new Vector2(7.071068f, -7.071068f).EqualsWithTolerence(b));
+ }
+
+ [Fact]
+ public void Vector2_Truncate_Test()
+ {
+ var a = new Vector2(10, 10);
+ var b = a.Truncate(5);
+
+ Assert.Equal(5f, b.Length(), 3);
+ }
+
+ [Fact]
+ public void Vector2_IsNaN_Test()
+ {
+ var a = new Vector2(float.NaN, 10);
+ var b = new Vector2(10, float.NaN);
+ var c = new Vector2(float.NaN, float.NaN);
+ var d = new Vector2(10, 10);
+
+ Assert.True(a.IsNaN());
+ Assert.True(b.IsNaN());
+ Assert.True(c.IsNaN());
+ Assert.False(d.IsNaN());
+ }
+
+ [Fact]
+ public void Vector2_ToAngle_Test()
+ {
+ var a = new Vector2(0, -10);
+ var b = new Vector2(10, 0);
+ var c = -Vector2.UnitY.Rotate(MathHelper.ToRadians(45));
+
+ Assert.Equal(MathHelper.ToRadians(0), a.ToAngle());
+ Assert.Equal(MathHelper.ToRadians(90), b.ToAngle());
+ Assert.Equal(MathHelper.ToRadians(45), c.ToAngle());
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/BoxingViewportAdapterTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/BoxingViewportAdapterTests.cs
new file mode 100644
index 0000000..665fea4
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/BoxingViewportAdapterTests.cs
@@ -0,0 +1,41 @@
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.ViewportAdapters;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.ViewportAdapters
+//{
+//
+// public class BoxingViewportAdapterTests
+// {
+// [Fact]
+// public void BoxingViewportAdapter_Letterbox_Test()
+// {
+// var gameWindow = new MockGameWindow();
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var viewportAdapter = new BoxingViewportAdapter(gameWindow, graphicsDevice, 800, 480);
+
+// graphicsDevice.Viewport = new Viewport(0, 0, 1024, 768);
+// viewportAdapter.Reset();
+
+// Assert.Equal(1024, graphicsDevice.Viewport.Width);
+// Assert.Equal(614, graphicsDevice.Viewport.Height);
+// Assert.Equal(BoxingMode.Letterbox, viewportAdapter.BoxingMode);
+// }
+
+// [Fact]
+// public void BoxingViewportAdapter_Pillarbox_Test()
+// {
+// var gameWindow = new MockGameWindow();
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var viewportAdapter = new BoxingViewportAdapter(gameWindow, graphicsDevice, 800, 480);
+
+// graphicsDevice.Viewport = new Viewport(0, 0, 900, 500);
+// viewportAdapter.Reset();
+
+// Assert.Equal(833, graphicsDevice.Viewport.Width);
+// Assert.Equal(500, graphicsDevice.Viewport.Height);
+// Assert.Equal(BoxingMode.Pillarbox, viewportAdapter.BoxingMode);
+// }
+
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/DefaultViewportAdapterTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/DefaultViewportAdapterTests.cs
new file mode 100644
index 0000000..1262119
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/ViewportAdapters/DefaultViewportAdapterTests.cs
@@ -0,0 +1,26 @@
+//using Microsoft.Xna.Framework;
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.ViewportAdapters;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.ViewportAdapters
+//{
+//
+// public class DefaultViewportAdapterTests
+// {
+// [Fact]
+// public void DefaultViewportAdapter_Test()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var viewportAdapter = new DefaultViewportAdapter(graphicsDevice);
+
+// graphicsDevice.Viewport = new Viewport(0, 0, 1024, 768);
+
+// Assert.Equal(1024, viewportAdapter.ViewportWidth);
+// Assert.Equal(768, viewportAdapter.ViewportHeight);
+// Assert.Equal(viewportAdapter.ViewportWidth, viewportAdapter.VirtualWidth);
+// Assert.Equal(viewportAdapter.ViewportHeight, viewportAdapter.VirtualHeight);
+// Assert.Equal(Matrix.Identity, viewportAdapter.GetScaleMatrix());
+// }
+// }
+//} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/WithinDeltaEqualityComparer.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/WithinDeltaEqualityComparer.cs
new file mode 100644
index 0000000..6ab17de
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tests/WithinDeltaEqualityComparer.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+
+namespace MonoGame.Extended.Tests;
+
+public class WithinDeltaEqualityComparer : IEqualityComparer<float>
+{
+ private readonly float _delta;
+
+ public WithinDeltaEqualityComparer(float delta)
+ {
+ _delta = delta;
+ }
+
+ public bool Equals(float x, float y)
+ {
+ return Math.Abs(x - y) < _delta;
+ }
+
+ public int GetHashCode(float obj)
+ {
+ return obj.GetHashCode();
+ }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/FullMapRendererTest.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/FullMapRendererTest.cs
new file mode 100644
index 0000000..3f8ae1d
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/FullMapRendererTest.cs
@@ -0,0 +1,291 @@
+//using Microsoft.Xna.Framework;
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.Shapes;
+//using MonoGame.Extended.TextureAtlases;
+//using MonoGame.Extended.Tiled;
+//using MonoGame.Extended.Tiled.Graphics;
+//using NSubstitute;
+//using Xunit;
+
+//namespace MonoGame.Extended.Tests.Tiled.Renderers
+//{
+//
+// public class FullMapRendererTest
+// {
+// [Fact]
+// public void Draw_MapObjectLayer_MissingGID_NoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+
+// IShapeF shape = new RectangleF(1, 1, 1, 1);
+// TiledObject[] objs =
+// {
+// new TiledObject(TiledObjectType.Tile, 1, null, shape, 1, 1) { IsVisible = true },
+// };
+
+// var layer = new TiledObjectLayer("object", objs);
+// m.AddLayer(layer);
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_MapObjectLayer_ShapeObject_NoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+
+// IShapeF shape = new RectangleF(1, 1, 1, 1);
+// TiledObject[] objs =
+// {
+// new TiledObject(TiledObjectType.Rectangle, 1, 1, shape, 1, 1) { IsVisible = true },
+// };
+
+// var layer = new TiledObjectLayer("object", objs);
+// m.AddLayer(layer);
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_MapObjectLayer_TileObject_OneGroup()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+
+// IShapeF shape = new RectangleF(1, 1, 1, 1);
+// TiledObject[] objs =
+// {
+// new TiledObject(TiledObjectType.Tile, 1, 1, shape, 1, 1) { IsVisible = true },
+// };
+
+// var layer = new TiledObjectLayer("object", objs);
+// m.AddLayer(layer);
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.NotNull(gd.Indices);
+// Assert.Equal(6, gd.Indices.IndexCount);
+// }
+
+// [Fact]
+// public void Draw_MapObjectLayer_NotVisible_NoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+
+// IShapeF shape = new RectangleF(1, 1, 1, 1);
+// TiledObject[] objs =
+// {
+// new TiledObject(TiledObjectType.Tile, 1, 1, shape, 1, 1) { IsVisible = false },
+// };
+
+// var layer = new TiledObjectLayer("object", objs);
+// m.AddLayer(layer);
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_MapObjectLayer_NoObjects_NoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+
+// TiledObject[] objs = {};
+
+// var layer = new TiledObjectLayer("object", objs);
+// m.AddLayer(layer);
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_MapTileLayer_TwoVisible_OneGroup()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+// m.CreateTileLayer("tile", 2, 2, new int[] { 1, 0, 1, 0 });
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.NotNull(gd.Indices);
+// Assert.Equal(12, gd.Indices.IndexCount);
+// }
+
+// [Fact]
+// public void Draw_MapTileLayer_AllBlank_NoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 2, 2, 32, 32);
+// m.CreateTileset(texture, 0, 32, 32, 4);
+// m.CreateTileLayer("tile", 2, 2, new int[] { 0, 0, 0, 0 });
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_MapImageLayer_OneGroup()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+
+// var m = new TiledMap("test", 10, 10, 32, 32);
+// m.CreateImageLayer("img", texture, new Vector2(100, 100));
+
+// r.Map = m;
+
+// r.Draw(new Matrix());
+
+// Assert.NotNull(gd.Indices);
+// Assert.Equal(6, gd.Indices.IndexCount);
+// }
+
+// [Fact]
+// public void Draw_MapNoGroups()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+// r.Map = new TiledMap("test", 10, 10, 32, 32);
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void Draw_NoMap()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var r = new MockRenderer(gd);
+
+// r.Draw(new Matrix());
+
+// Assert.IsNull(gd.Indices);
+// }
+
+// [Fact]
+// public void CreatePrimatives()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+// var region = Substitute.For<TextureRegion2D>(texture, 1, 1, 32, 32);
+
+// VertexPositionTexture[] vertices;
+// ushort[] indexes;
+
+// var r = new MockRenderer(gd);
+// r.CreatePrimitives(new Point(0, 0), region, 0, 0.5f, out vertices, out indexes);
+
+// Assert.Equal(4, vertices.Length);
+// Assert.Equal(new Vector3(0, 0, .5f), vertices[0].Position);
+// Assert.Equal(new Vector2(0.0234375f, 0.0234375f), vertices[0].TextureCoordinate);
+// Assert.Equal(new Vector3(32, 0, .5f), vertices[1].Position);
+// Assert.Equal(new Vector2(0.515625f, 0.0234375f), vertices[1].TextureCoordinate);
+// Assert.Equal(new Vector3(0, 32, .5f), vertices[2].Position);
+// Assert.Equal(new Vector2(0.0234375f, 0.515625f), vertices[2].TextureCoordinate);
+// Assert.Equal(new Vector3(32, 32, .5f), vertices[3].Position);
+// Assert.Equal(new Vector2(0.515625f, 0.515625f), vertices[3].TextureCoordinate);
+
+// CollectionAssert.Equal(new[] { 0, 1, 2, 1, 3, 2 }, indexes);
+// }
+
+// [Fact]
+// public void CreatePrimatives_Offset10()
+// {
+// var gd = TestHelper.CreateGraphicsDevice();
+// var texture = Substitute.For<Texture2D>(gd, 64, 64);
+// var region = Substitute.For<TextureRegion2D>(texture, 1, 1, 32, 32);
+
+// VertexPositionTexture[] vertices;
+// ushort[] indexes;
+
+// var r = new MockRenderer(gd);
+// r.CreatePrimitives(new Point(0, 0), region, 10, 0.5f, out vertices, out indexes);
+
+// Assert.Equal(4, vertices.Length);
+// Assert.Equal(new Vector3(0, 0, .5f), vertices[0].Position);
+// Assert.Equal(new Vector2(0.0234375f, 0.0234375f), vertices[0].TextureCoordinate);
+// Assert.Equal(new Vector3(32, 0, .5f), vertices[1].Position);
+// Assert.Equal(new Vector2(0.515625f, 0.0234375f), vertices[1].TextureCoordinate);
+// Assert.Equal(new Vector3(0, 32, .5f), vertices[2].Position);
+// Assert.Equal(new Vector2(0.0234375f, 0.515625f), vertices[2].TextureCoordinate);
+// Assert.Equal(new Vector3(32, 32, .5f), vertices[3].Position);
+// Assert.Equal(new Vector2(0.515625f, 0.515625f), vertices[3].TextureCoordinate);
+
+// CollectionAssert.Equal(new[] { 40, 41, 42, 41, 43, 42 }, indexes);
+// }
+// }
+
+// internal class MockRenderer : TiledMapRenderer
+// {
+// public MockRenderer(GraphicsDevice graphicsDevice)
+// : base(graphicsDevice)
+// {
+// }
+
+// public void CreatePrimitives(Point point, TextureRegion2D region, int offset, float depth,
+// out VertexPositionTexture[] vertices, out ushort[] indexes)
+// {
+// base.CreatePrimitives(point, region, offset, depth, out vertices, out indexes);
+// }
+
+// public new void Draw(Matrix viewMatrix)
+// {
+// base.Draw(viewMatrix);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj
new file mode 100644
index 0000000..32a97f2
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Tiled\MonoGame.Extended.Tiled.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/TiledTilesetTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/TiledTilesetTests.cs
new file mode 100644
index 0000000..e3e5084
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tiled.Tests/TiledTilesetTests.cs
@@ -0,0 +1,130 @@
+//#region
+
+//using Microsoft.Xna.Framework.Content.Pipeline;
+//using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
+//using Microsoft.Xna.Framework.Graphics;
+//using MonoGame.Extended.Tests;
+//using Xunit;
+
+//#endregion
+
+//namespace MonoGame.Extended.Tiled.Tests
+//{
+//
+// public class TiledTilesetTests
+// {
+// [Fact]
+// public void Constructor()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 64, 64);
+
+// var tiledTileset = new TiledMapTileset(texture, 10, 32, 32, 4, 0, 0);
+
+
+// //Assert.IsNull(tiledTileset.GetTileRegion(0));
+// }
+
+// [Fact]
+// public void GetTileRegion_BlankTile()
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 64, 64);
+
+// var tiledTileset = new TiledTileset(texture, 10, 32, 32, 4, 0, 0);
+
+
+// //Assert.IsNull(tiledTileset.GetTileRegion(0));
+// }
+
+// [Fact]
+// [TestCase(9, Result = false, Description = "Too low")]
+// [TestCase(10, Result = true, Description = "Min tile")]
+// [TestCase(11, Result = true, Description = "Middle tile")]
+// [TestCase(13, Result = true, Description = "Last tile")]
+// [TestCase(14, Result = false, Description = "Too high")]
+// public bool ContainsTileId(int id)
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 64, 64);
+
+// var tiledTileset = new TiledTileset(texture, 10, 32, 32, 4, 0, 0);
+
+// return tiledTileset.ContainsTileId(id);
+// }
+
+// [Fact]
+// public void Constructor_NoMargin([Values(0, 2)] int spacing)
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 64, 64);
+
+// var tiledTileset = new TiledTileset(texture, 1, 32, 32, 4, spacing, 0);
+
+// var region = tiledTileset.GetTileRegion(1);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(0, region.X);
+// Assert.Equal(0, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tiledTileset.GetTileRegion(2);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(spacing + 32, region.X);
+// Assert.Equal(0, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tiledTileset.GetTileRegion(3);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(0, region.X);
+// Assert.Equal(spacing + 32, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tiledTileset.GetTileRegion(4);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(spacing + 32, region.X);
+// Assert.Equal(spacing + 32, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+// }
+
+// [Fact]
+// public void Constructor_NoSpacing([Values(0, 2)] int margin)
+// {
+// var graphicsDevice = TestHelper.CreateGraphicsDevice();
+// var texture = new Texture2D(graphicsDevice, 64, 64);
+
+// var tileset = new TiledTileset(texture, 1, 32, 32, 4, 0, margin);
+
+// var region = tileset.GetTileRegion(1);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(margin, region.X);
+// Assert.Equal(margin, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tileset.GetTileRegion(2);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(margin + 32, region.X);
+// Assert.Equal(margin, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tileset.GetTileRegion(3);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(margin, region.X);
+// Assert.Equal(margin + 32, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+
+// region = tileset.GetTileRegion(4);
+// Assert.Equal(texture, region.Texture);
+// Assert.Equal(margin + 32, region.X);
+// Assert.Equal(margin + 32, region.Y);
+// Assert.Equal(32, region.Width);
+// Assert.Equal(32, region.Height);
+// }
+// }
+//}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/ColorHandler.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/ColorHandler.cs
new file mode 100644
index 0000000..a4cddc0
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/ColorHandler.cs
@@ -0,0 +1,8 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tweening.Tests;
+
+public class ColorHandler
+{
+ public Color Color { get; set; }
+}
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj
new file mode 100644
index 0000000..88ce44f
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\MonoGame.Extended.Tweening\MonoGame.Extended.Tweening.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/TweenerTests.cs b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/TweenerTests.cs
new file mode 100644
index 0000000..0fbfded
--- /dev/null
+++ b/Plugins/MonoGame.Extended/tests/MonoGame.Extended.Tweening.Tests/TweenerTests.cs
@@ -0,0 +1,15 @@
+using Microsoft.Xna.Framework;
+using Xunit;
+
+namespace MonoGame.Extended.Tweening.Tests;
+
+public class TweenerTests
+{
+ [Fact]
+ public void TweenerTweenToSuccessTest()
+ {
+ var tweener = new Tweener();
+ var obj = new ColorHandler();
+ tweener.TweenTo(obj, x => x.Color, Color.Red, 2f);
+ }
+}