From eab9d69c0eb6fbf6e97c3850e6428c3c795207ef Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:14:21 +0500 Subject: [PATCH 1/6] create cloud layouter --- cs/TagCloud/CircularCloudLayouter.cs | 24 +++++++++++++++++ cs/TagCloud/TagCloud.csproj | 9 +++++++ cs/TagCloudTests/CloudTests.cs | 38 +++++++++++++++++++++++++++ cs/TagCloudTests/GlobalUsings.cs | 1 + cs/TagCloudTests/TagCloudTests.csproj | 25 ++++++++++++++++++ cs/tdd.sln | 12 +++++++++ 6 files changed, 109 insertions(+) create mode 100644 cs/TagCloud/CircularCloudLayouter.cs create mode 100644 cs/TagCloud/TagCloud.csproj create mode 100644 cs/TagCloudTests/CloudTests.cs create mode 100644 cs/TagCloudTests/GlobalUsings.cs create mode 100644 cs/TagCloudTests/TagCloudTests.csproj diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs new file mode 100644 index 000000000..ca6932b85 --- /dev/null +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -0,0 +1,24 @@ +using System.Drawing; + +namespace TagCloud; + +public class CircularCloudLayouter +{ + private List rectangles; + private Point center; + + public IEnumerable Rectangles => rectangles; + + public CircularCloudLayouter(Point center) + { + this.center = center; + rectangles = new List(); + } + + public Rectangle PutNextRectangle(Size rectangleSize) + { + var rectangle = new Rectangle(center, rectangleSize); + rectangles.Add(rectangle); + return rectangle; + } +} \ No newline at end of file diff --git a/cs/TagCloud/TagCloud.csproj b/cs/TagCloud/TagCloud.csproj new file mode 100644 index 000000000..6836c6808 --- /dev/null +++ b/cs/TagCloud/TagCloud.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/cs/TagCloudTests/CloudTests.cs b/cs/TagCloudTests/CloudTests.cs new file mode 100644 index 000000000..5f45d08a5 --- /dev/null +++ b/cs/TagCloudTests/CloudTests.cs @@ -0,0 +1,38 @@ +using System.Drawing; +using FluentAssertions; +using TagCloud; + +namespace TagCloudTests; + +public class CloudTests +{ + private CircularCloudLayouter layouter; + private Point center = new Point(0, 0); + + [SetUp] + public void Setup() + { + layouter = new CircularCloudLayouter(center); + } + + [Test] + public void ShouldReturnEmptyList_WhenCreated() + { + layouter.Rectangles.Should().BeEmpty(); + } + + [Test] + public void ShouldReturnOneElementList_WhenAddOne() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(1); + } + + [Test] + public void ShouldReturnTwoElementList_WhenAddTwo() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(2); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/GlobalUsings.cs b/cs/TagCloudTests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/cs/TagCloudTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudTests.csproj b/cs/TagCloudTests/TagCloudTests.csproj new file mode 100644 index 000000000..ccac200e3 --- /dev/null +++ b/cs/TagCloudTests/TagCloudTests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + diff --git a/cs/tdd.sln b/cs/tdd.sln index c8f523d63..839ee2a35 100644 --- a/cs/tdd.sln +++ b/cs/tdd.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BowlingGame", "BowlingGame\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloud", "TagCloud\TagCloud.csproj", "{449E90CC-3D52-4E1C-8BB9-73C93CA79781}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagCloudTests", "TagCloudTests\TagCloudTests.csproj", "{C7C10229-7576-41A9-B0A0-2A9939985DB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5108E20-2ACF-4ED9-84FE-2A718050FC94}.Release|Any CPU.Build.0 = Release|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {449E90CC-3D52-4E1C-8BB9-73C93CA79781}.Release|Any CPU.Build.0 = Release|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7C10229-7576-41A9-B0A0-2A9939985DB0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 39f073bb3c2f0e4fbe41a9fd34ecedd4d3702edf Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:05:43 +0500 Subject: [PATCH 2/6] add method to detect intersected rectangles in collection --- cs/TagCloud/Extensions.cs | 25 ++++++++ .../{CloudTests.cs => CloudTests_Should.cs} | 18 ++++-- .../HasIntersectedRectangleTests_Should.cs | 60 +++++++++++++++++++ 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 cs/TagCloud/Extensions.cs rename cs/TagCloudTests/{CloudTests.cs => CloudTests_Should.cs} (62%) create mode 100644 cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs diff --git a/cs/TagCloud/Extensions.cs b/cs/TagCloud/Extensions.cs new file mode 100644 index 000000000..e7407c5b0 --- /dev/null +++ b/cs/TagCloud/Extensions.cs @@ -0,0 +1,25 @@ +using System.Drawing; + +namespace TagCloudTests; + +public static class Extensions +{ + public static IEnumerable> CartesianProduct(this IEnumerable source) + { + var array = source.ToArray(); + if (array.Length == 0 || array.Length == 1) + throw new ArgumentException(); + for (int i = 0; i < array.Length; i++) + for (int j = i + 1; j < array.Length; j++) + yield return Tuple.Create(array[i], array[j]); + } + + public static bool HasIntersectedRectangles(this IEnumerable rectangles) + { + foreach (var (first, second) in rectangles.CartesianProduct()) + if (first.IntersectsWith(second)) + return true; + + return false; + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/CloudTests.cs b/cs/TagCloudTests/CloudTests_Should.cs similarity index 62% rename from cs/TagCloudTests/CloudTests.cs rename to cs/TagCloudTests/CloudTests_Should.cs index 5f45d08a5..b85e14311 100644 --- a/cs/TagCloudTests/CloudTests.cs +++ b/cs/TagCloudTests/CloudTests_Should.cs @@ -4,7 +4,7 @@ namespace TagCloudTests; -public class CloudTests +public class CloudTests_Should { private CircularCloudLayouter layouter; private Point center = new Point(0, 0); @@ -16,23 +16,33 @@ public void Setup() } [Test] - public void ShouldReturnEmptyList_WhenCreated() + public void ReturnEmptyList_WhenCreated() { layouter.Rectangles.Should().BeEmpty(); } [Test] - public void ShouldReturnOneElementList_WhenAddOne() + public void ReturnOneElementList_WhenAddOne() { layouter.PutNextRectangle(new Size(1, 1)); layouter.Rectangles.Count().Should().Be(1); } [Test] - public void ShouldReturnTwoElementList_WhenAddTwo() + public void ReturnTwoElementList_WhenAddTwo() { layouter.PutNextRectangle(new Size(1, 1)); layouter.PutNextRectangle(new Size(1, 1)); layouter.Rectangles.Count().Should().Be(2); + NotIntersectedAssetration(); + } + + public void NotIntersectedAssetration() + { + layouter + .Rectangles + .HasIntersectedRectangles() + .Should() + .BeFalse(); } } \ No newline at end of file diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs new file mode 100644 index 000000000..6c4d4aa0e --- /dev/null +++ b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs @@ -0,0 +1,60 @@ +using System.Drawing; +using FluentAssertions; + +namespace TagCloudTests; + +public class HasIntersectedRectangleTests_Should +{ + public static TestCaseData[] intersectedRectanglesTestCases = + { + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) + .SetName("WhenFirstEqualsSecond"), + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstInSecond"), + new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByLeftSide"), + new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByRightSide"), + new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByTopSide"), + new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByBottomSide"), + new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) + .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), + new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) + .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") + }; + + public static TestCaseData[] notIntersectedRectanglesTestCases = + { + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), + new TestCaseData(new Rectangle(0,0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirst_LeftSecondLeftBorder"), + new TestCaseData(new Rectangle(0,0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder") + }; + + [TestCaseSource(nameof(intersectedRectanglesTestCases))] + public void ReturnTrue(Rectangle first, Rectangle second) + { + new[] { first, second }.HasIntersectedRectangles().Should().BeTrue(); + new[] { second, first }.HasIntersectedRectangles().Should().BeTrue(); + } + + [TestCaseSource(nameof(notIntersectedRectanglesTestCases))] + public void ReturnFalse(Rectangle first, Rectangle second) + { + new[] { first, second }.HasIntersectedRectangles().Should().BeFalse(); + new[] { second, first }.HasIntersectedRectangles().Should().BeFalse(); + } + + [Test] + public void ThrowException_ThenEmptyOrConainsOneElement() + { + Assert.Throws(() => Array.Empty().HasIntersectedRectangles()); + Assert.Throws(() => new[] { new Rectangle(0, 0, 1, 1) }.HasIntersectedRectangles()); + } +} \ No newline at end of file From 780f1bf3c15735e193bef7cb0e653e89c085531c Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:59:54 +0500 Subject: [PATCH 3/6] add cloud shaper, randomized tests and error logging --- cs/TagCloud/CircularCloudLayouter.cs | 8 +- cs/TagCloud/ICloudShaper.cs | 8 ++ cs/TagCloud/SpiralCloudShaper.cs | 30 ++++++++ cs/TagCloudTests/CloudTests_Should.cs | 70 ++++++++++++++++++ ...ests_Should.AlwaysFail(5,10,10,20,100).jpg | Bin 0 -> 4968 bytes .../HasIntersectedRectangleTests_Should.cs | 4 - cs/TagCloudTests/TagCloudTests.csproj | 5 ++ 7 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 cs/TagCloud/ICloudShaper.cs create mode 100644 cs/TagCloud/SpiralCloudShaper.cs create mode 100644 cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs index ca6932b85..049e33d06 100644 --- a/cs/TagCloud/CircularCloudLayouter.cs +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -6,6 +6,7 @@ public class CircularCloudLayouter { private List rectangles; private Point center; + private ICloudShaper shaper; public IEnumerable Rectangles => rectangles; @@ -13,11 +14,16 @@ public CircularCloudLayouter(Point center) { this.center = center; rectangles = new List(); + shaper = new SpiralCloudShaper(this.center); } public Rectangle PutNextRectangle(Size rectangleSize) { - var rectangle = new Rectangle(center, rectangleSize); + var rectangle = shaper.GetNextPossibleRectangle(rectangleSize); + while (Rectangles.Any(rect => rect.IntersectsWith(rectangle))) + { + rectangle = shaper.GetNextPossibleRectangle(rectangleSize); + } rectangles.Add(rectangle); return rectangle; } diff --git a/cs/TagCloud/ICloudShaper.cs b/cs/TagCloud/ICloudShaper.cs new file mode 100644 index 000000000..8fbbb7f57 --- /dev/null +++ b/cs/TagCloud/ICloudShaper.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloud; + +public interface ICloudShaper +{ + Rectangle GetNextPossibleRectangle(Size size); +} \ No newline at end of file diff --git a/cs/TagCloud/SpiralCloudShaper.cs b/cs/TagCloud/SpiralCloudShaper.cs new file mode 100644 index 000000000..d01fd3ee6 --- /dev/null +++ b/cs/TagCloud/SpiralCloudShaper.cs @@ -0,0 +1,30 @@ +using System.Drawing; + +namespace TagCloud; + +public class SpiralCloudShaper : ICloudShaper +{ + private Point center; + private double currentAngle; + private double coefficient; + private double deltaAngle; + + public double Radius => coefficient * currentAngle; + + public SpiralCloudShaper(Point center, double coefficient = 1, double deltaAngle = 0.1) + { + this.center = center; + this.coefficient = coefficient; + this.deltaAngle = deltaAngle; + currentAngle = 0; + } + public Rectangle GetNextPossibleRectangle(Size size) + { + currentAngle += deltaAngle; + var position = new Point( + center.X + (int)(Radius * Math.Cos(currentAngle)), + center.Y + (int)(Radius * Math.Sin(currentAngle)) + ); + return new Rectangle(position, size); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/CloudTests_Should.cs b/cs/TagCloudTests/CloudTests_Should.cs index b85e14311..a72adb4fb 100644 --- a/cs/TagCloudTests/CloudTests_Should.cs +++ b/cs/TagCloudTests/CloudTests_Should.cs @@ -1,11 +1,14 @@ using System.Drawing; using FluentAssertions; +using NUnit.Framework.Interfaces; using TagCloud; namespace TagCloudTests; public class CloudTests_Should { + private const string RelativePathToFailDirectory = @"..\..\..\Fails"; + private CircularCloudLayouter layouter; private Point center = new Point(0, 0); @@ -15,6 +18,13 @@ public void Setup() layouter = new CircularCloudLayouter(center); } + [TearDown] + public void Tear_Down() + { + if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) + SaveIncorrectResultsToJpg(); + } + [Test] public void ReturnEmptyList_WhenCreated() { @@ -37,6 +47,46 @@ public void ReturnTwoElementList_WhenAddTwo() NotIntersectedAssetration(); } + [TestCase(1, 1, 100, TestName = "WithSquareShape")] + [TestCase(20, 10, 100, TestName = "WithRectangleShape")] + public void AddManyRectangles_WithConstantSize(int width, int height, int count) + { + var size = new Size(width, height); + for (int i = 0; i < count; i++) + layouter.PutNextRectangle(size); + + layouter.Rectangles.Count().Should().Be(count); + NotIntersectedAssetration(); + } + + [TestCase(5, 10, 2, 4, 100)] + public void AddManyRectangles_WithVariableSize(int widthMin, int widthMax, int heightMin, int heightMax, int count) + { + var rnd = new Random(DateTime.Now.Microsecond); + + for (int i = 0; i < count; i++) + { + var size = new Size(rnd.Next(widthMin, widthMax), rnd.Next(heightMin, heightMax)); + layouter.PutNextRectangle(size); + } + + layouter.Rectangles.Count().Should().Be(count); + NotIntersectedAssetration(); + } + + [TestCase(5, 10, 10, 20, 100)] + public void AlwaysFail(int widthMin, int widthMax, int heightMin, int heightMax, int count) + { + var rnd = new Random(DateTime.Now.Microsecond); + for (int i = 0; i < count; i++) + { + var size = new Size(rnd.Next(widthMin, widthMax), rnd.Next(heightMin, heightMax)); + layouter.PutNextRectangle(size); + } + + Assert.Fail(); + } + public void NotIntersectedAssetration() { layouter @@ -45,4 +95,24 @@ public void NotIntersectedAssetration() .Should() .BeFalse(); } + + private void SaveIncorrectResultsToJpg() + { + var rectangles = layouter.Rectangles; + if (rectangles.Count() == 0) + return; + var minX = rectangles.Min(rect => rect.X); + var maxX = rectangles.Max(rect => rect.Right); + var minY = rectangles.Min(rect => rect.Top); + var maxY = rectangles.Max(rect => rect.Bottom); + var bitmap = new System.Drawing.Bitmap(maxX - minX + 2, maxY - minY + 2); + var graphics = Graphics.FromImage(bitmap); + graphics.DrawRectangles(new Pen(Color.Red, 1), + rectangles.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) + .ToArray()); + var pathToFile = @$"{RelativePathToFailDirectory}\{TestContext.CurrentContext.Test.FullName}.jpg"; + var absolutePath = Path.GetFullPath(pathToFile); + bitmap.Save(pathToFile); + Console.WriteLine($"Tag cloud visualization saved to file {absolutePath}"); + } } \ No newline at end of file diff --git a/cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg b/cs/TagCloudTests/Fails/TagCloudTests.CloudTests_Should.AlwaysFail(5,10,10,20,100).jpg new file mode 100644 index 0000000000000000000000000000000000000000..79ee9f89c3ce6bcc1c41eebe19290f6fa2406603 GIT binary patch literal 4968 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 + + + + + From d693b708c961337416d37ae0c003cc9dadbaf662 Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Sat, 2 Dec 2023 22:03:13 +0500 Subject: [PATCH 4/6] small fixes --- cs/TagCloud/CircularCloudLayouter.cs | 7 +- cs/TagCloud/Extensions.cs | 8 +- cs/TagCloudTests/CloudTests_Should.cs | 5 +- .../HasIntersectedRectangleTests_Should.cs | 73 ++++++++++--------- 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs index 049e33d06..aa0c5584c 100644 --- a/cs/TagCloud/CircularCloudLayouter.cs +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -7,9 +7,9 @@ public class CircularCloudLayouter private List rectangles; private Point center; private ICloudShaper shaper; - + public IEnumerable Rectangles => rectangles; - + public CircularCloudLayouter(Point center) { this.center = center; @@ -21,9 +21,8 @@ public Rectangle PutNextRectangle(Size rectangleSize) { var rectangle = shaper.GetNextPossibleRectangle(rectangleSize); while (Rectangles.Any(rect => rect.IntersectsWith(rectangle))) - { rectangle = shaper.GetNextPossibleRectangle(rectangleSize); - } + rectangles.Add(rectangle); return rectangle; } diff --git a/cs/TagCloud/Extensions.cs b/cs/TagCloud/Extensions.cs index e7407c5b0..d764ea274 100644 --- a/cs/TagCloud/Extensions.cs +++ b/cs/TagCloud/Extensions.cs @@ -7,13 +7,13 @@ public static class Extensions public static IEnumerable> CartesianProduct(this IEnumerable source) { var array = source.ToArray(); - if (array.Length == 0 || array.Length == 1) + if (array.Length <= 1) throw new ArgumentException(); for (int i = 0; i < array.Length; i++) - for (int j = i + 1; j < array.Length; j++) - yield return Tuple.Create(array[i], array[j]); + for (int j = i + 1; j < array.Length; j++) + yield return Tuple.Create(array[i], array[j]); } - + public static bool HasIntersectedRectangles(this IEnumerable rectangles) { foreach (var (first, second) in rectangles.CartesianProduct()) diff --git a/cs/TagCloudTests/CloudTests_Should.cs b/cs/TagCloudTests/CloudTests_Should.cs index a72adb4fb..78390fae0 100644 --- a/cs/TagCloudTests/CloudTests_Should.cs +++ b/cs/TagCloudTests/CloudTests_Should.cs @@ -8,7 +8,7 @@ namespace TagCloudTests; public class CloudTests_Should { private const string RelativePathToFailDirectory = @"..\..\..\Fails"; - + private CircularCloudLayouter layouter; private Point center = new Point(0, 0); @@ -101,15 +101,18 @@ private void SaveIncorrectResultsToJpg() var rectangles = layouter.Rectangles; if (rectangles.Count() == 0) return; + var minX = rectangles.Min(rect => rect.X); var maxX = rectangles.Max(rect => rect.Right); var minY = rectangles.Min(rect => rect.Top); var maxY = rectangles.Max(rect => rect.Bottom); + var bitmap = new System.Drawing.Bitmap(maxX - minX + 2, maxY - minY + 2); var graphics = Graphics.FromImage(bitmap); graphics.DrawRectangles(new Pen(Color.Red, 1), rectangles.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) .ToArray()); + var pathToFile = @$"{RelativePathToFailDirectory}\{TestContext.CurrentContext.Test.FullName}.jpg"; var absolutePath = Path.GetFullPath(pathToFile); bitmap.Save(pathToFile); diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs index 98995faef..af7f91355 100644 --- a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs +++ b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs @@ -1,50 +1,51 @@ using System.Drawing; using FluentAssertions; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace TagCloudTests; public class HasIntersectedRectangleTests_Should { - public static TestCaseData[] intersectedRectanglesTestCases = - { - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) - .SetName("WhenFirstEqualsSecond"), - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstInSecond"), - new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByLeftSide"), - new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByRightSide"), - new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByTopSide"), - new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByBottomSide"), - new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) - .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), - new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) - .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") - }; + #region TestData + + public static IEnumerable intersectedRectanglesTestCases = + new[] + { + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) + .SetName("WhenFirstEqualsSecond"), + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstInSecond"), + new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByLeftSide"), + new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByRightSide"), + new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByTopSide"), + new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByBottomSide"), + new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) + .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), + new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) + .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") + }.Select(testCase => testCase.Returns(true)); + + public static IEnumerable notIntersectedRectanglesTestCases = + new[] + { + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), + }.Select(testCase => testCase.Returns(false)); + + #endregion - public static TestCaseData[] notIntersectedRectanglesTestCases = - { - new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) - .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), - new TestCaseData(new Rectangle(0,0, 1, 1), new Rectangle(0, 1, 1, 1)) - .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), - }; - [TestCaseSource(nameof(intersectedRectanglesTestCases))] - public void ReturnTrue(Rectangle first, Rectangle second) - { - new[] { first, second }.HasIntersectedRectangles().Should().BeTrue(); - new[] { second, first }.HasIntersectedRectangles().Should().BeTrue(); - } - [TestCaseSource(nameof(notIntersectedRectanglesTestCases))] - public void ReturnFalse(Rectangle first, Rectangle second) + public bool ReturnValue(Rectangle first, Rectangle second) { - new[] { first, second }.HasIntersectedRectangles().Should().BeFalse(); - new[] { second, first }.HasIntersectedRectangles().Should().BeFalse(); + return new[] { first, second }.HasIntersectedRectangles() + && new[] { second, first }.HasIntersectedRectangles(); } [Test] From 6dbc9992ab1d92ba904d04934ec94fc6a7e347ff Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:53:07 +0500 Subject: [PATCH 5/6] first fix --- cs/TagCloud/CircularCloudLayouter.cs | 16 ++++-- cs/TagCloud/ICloudShaper.cs | 2 +- cs/TagCloud/SpiralCloudShaper.cs | 25 +++++++-- cs/TagCloudTests/CloudTests_Should.cs | 52 +++++-------------- cs/{TagCloud => TagCloudTests}/Extensions.cs | 0 .../HasIntersectedRectangleTests_Should.cs | 48 ++++------------- cs/TagCloudTests/SpiralCloudShaper_Should.cs | 17 ++++++ cs/TagCloudTests/TagCloudDrawer.cs | 31 +++++++++++ .../IntersectedRectanglesTestCases.cs | 33 ++++++++++++ .../NotIntersectedRectanglesTestCases.cs | 21 ++++++++ 10 files changed, 159 insertions(+), 86 deletions(-) rename cs/{TagCloud => TagCloudTests}/Extensions.cs (100%) create mode 100644 cs/TagCloudTests/SpiralCloudShaper_Should.cs create mode 100644 cs/TagCloudTests/TagCloudDrawer.cs create mode 100644 cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs create mode 100644 cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs index aa0c5584c..193369fb9 100644 --- a/cs/TagCloud/CircularCloudLayouter.cs +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -17,12 +17,20 @@ public CircularCloudLayouter(Point center) shaper = new SpiralCloudShaper(this.center); } - public Rectangle PutNextRectangle(Size rectangleSize) + public Rectangle PutNextRectangle(Size size) { - var rectangle = shaper.GetNextPossibleRectangle(rectangleSize); + if (size.Width <= 0) + throw new ArgumentException("Size width must be positive number"); + if (size.Height <= 0) + throw new ArgumentException("Size height must be positive number"); + + var point = shaper.GetNextPossiblePoint(); + var rectangle = new Rectangle(point, size); while (Rectangles.Any(rect => rect.IntersectsWith(rectangle))) - rectangle = shaper.GetNextPossibleRectangle(rectangleSize); - + { + point = shaper.GetNextPossiblePoint(); + rectangle = new Rectangle(point, size); + } rectangles.Add(rectangle); return rectangle; } diff --git a/cs/TagCloud/ICloudShaper.cs b/cs/TagCloud/ICloudShaper.cs index 8fbbb7f57..24270e26d 100644 --- a/cs/TagCloud/ICloudShaper.cs +++ b/cs/TagCloud/ICloudShaper.cs @@ -4,5 +4,5 @@ namespace TagCloud; public interface ICloudShaper { - Rectangle GetNextPossibleRectangle(Size size); + Point GetNextPossiblePoint(); } \ No newline at end of file diff --git a/cs/TagCloud/SpiralCloudShaper.cs b/cs/TagCloud/SpiralCloudShaper.cs index d01fd3ee6..fb963c892 100644 --- a/cs/TagCloud/SpiralCloudShaper.cs +++ b/cs/TagCloud/SpiralCloudShaper.cs @@ -5,6 +5,8 @@ namespace TagCloud; public class SpiralCloudShaper : ICloudShaper { private Point center; + private Point previousPoint; + private double currentAngle; private double coefficient; private double deltaAngle; @@ -14,17 +16,34 @@ public class SpiralCloudShaper : ICloudShaper public SpiralCloudShaper(Point center, double coefficient = 1, double deltaAngle = 0.1) { this.center = center; + if (coefficient <= 0) + throw new ArgumentException("Spiral coefficient must be positive number"); this.coefficient = coefficient; + + if (deltaAngle <= 0) + throw new ArgumentException("Spiral delta angle must be positive number"); this.deltaAngle = deltaAngle; + currentAngle = 0; + previousPoint = center; } - public Rectangle GetNextPossibleRectangle(Size size) + public Point GetNextPossiblePoint() { currentAngle += deltaAngle; - var position = new Point( + var position = CalculatePointByCurrentAngle(); + while (position == previousPoint) + { + currentAngle += deltaAngle; + position = CalculatePointByCurrentAngle(); + } + return position; + } + + private Point CalculatePointByCurrentAngle() + { + return new Point( center.X + (int)(Radius * Math.Cos(currentAngle)), center.Y + (int)(Radius * Math.Sin(currentAngle)) ); - return new Rectangle(position, size); } } \ No newline at end of file diff --git a/cs/TagCloudTests/CloudTests_Should.cs b/cs/TagCloudTests/CloudTests_Should.cs index 78390fae0..4de13e63a 100644 --- a/cs/TagCloudTests/CloudTests_Should.cs +++ b/cs/TagCloudTests/CloudTests_Should.cs @@ -7,14 +7,12 @@ namespace TagCloudTests; public class CloudTests_Should { - private const string RelativePathToFailDirectory = @"..\..\..\Fails"; - private CircularCloudLayouter layouter; - private Point center = new Point(0, 0); [SetUp] public void Setup() { + var center = new Point(0, 0); layouter = new CircularCloudLayouter(center); } @@ -22,7 +20,7 @@ public void Setup() public void Tear_Down() { if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) - SaveIncorrectResultsToJpg(); + TagCloudDrawer.Draw(layouter); } [Test] @@ -44,7 +42,7 @@ public void ReturnTwoElementList_WhenAddTwo() layouter.PutNextRectangle(new Size(1, 1)); layouter.PutNextRectangle(new Size(1, 1)); layouter.Rectangles.Count().Should().Be(2); - NotIntersectedAssetration(); + NotIntersectedAssertation(); } [TestCase(1, 1, 100, TestName = "WithSquareShape")] @@ -56,10 +54,11 @@ public void AddManyRectangles_WithConstantSize(int width, int height, int count) layouter.PutNextRectangle(size); layouter.Rectangles.Count().Should().Be(count); - NotIntersectedAssetration(); + NotIntersectedAssertation(); } [TestCase(5, 10, 2, 4, 100)] + [TestCase(20, 40, 20, 40, 200)] public void AddManyRectangles_WithVariableSize(int widthMin, int widthMax, int heightMin, int heightMax, int count) { var rnd = new Random(DateTime.Now.Microsecond); @@ -71,23 +70,18 @@ public void AddManyRectangles_WithVariableSize(int widthMin, int widthMax, int h } layouter.Rectangles.Count().Should().Be(count); - NotIntersectedAssetration(); + NotIntersectedAssertation(); } - [TestCase(5, 10, 10, 20, 100)] - public void AlwaysFail(int widthMin, int widthMax, int heightMin, int heightMax, int count) + [TestCase(-1, 1, TestName = "WithNegativeWidth")] + [TestCase(1, -1, TestName = "WithNegativeHeight")] + [TestCase(-1, -1, TestName = "WithNegativeWidthAndHeight")] + public void Throw_ThenTryPutRectangle(int width, int height) { - var rnd = new Random(DateTime.Now.Microsecond); - for (int i = 0; i < count; i++) - { - var size = new Size(rnd.Next(widthMin, widthMax), rnd.Next(heightMin, heightMax)); - layouter.PutNextRectangle(size); - } - - Assert.Fail(); + Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); } - public void NotIntersectedAssetration() + public void NotIntersectedAssertation() { layouter .Rectangles @@ -96,26 +90,4 @@ public void NotIntersectedAssetration() .BeFalse(); } - private void SaveIncorrectResultsToJpg() - { - var rectangles = layouter.Rectangles; - if (rectangles.Count() == 0) - return; - - var minX = rectangles.Min(rect => rect.X); - var maxX = rectangles.Max(rect => rect.Right); - var minY = rectangles.Min(rect => rect.Top); - var maxY = rectangles.Max(rect => rect.Bottom); - - var bitmap = new System.Drawing.Bitmap(maxX - minX + 2, maxY - minY + 2); - var graphics = Graphics.FromImage(bitmap); - graphics.DrawRectangles(new Pen(Color.Red, 1), - rectangles.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) - .ToArray()); - - var pathToFile = @$"{RelativePathToFailDirectory}\{TestContext.CurrentContext.Test.FullName}.jpg"; - var absolutePath = Path.GetFullPath(pathToFile); - bitmap.Save(pathToFile); - Console.WriteLine($"Tag cloud visualization saved to file {absolutePath}"); - } } \ No newline at end of file diff --git a/cs/TagCloud/Extensions.cs b/cs/TagCloudTests/Extensions.cs similarity index 100% rename from cs/TagCloud/Extensions.cs rename to cs/TagCloudTests/Extensions.cs diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs index af7f91355..0e5b95678 100644 --- a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs +++ b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs @@ -1,47 +1,14 @@ using System.Drawing; using FluentAssertions; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TagCloudTests.TestData; namespace TagCloudTests; public class HasIntersectedRectangleTests_Should { - #region TestData - - public static IEnumerable intersectedRectanglesTestCases = - new[] - { - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) - .SetName("WhenFirstEqualsSecond"), - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstInSecond"), - new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByLeftSide"), - new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByRightSide"), - new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByTopSide"), - new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByBottomSide"), - new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) - .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), - new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) - .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") - }.Select(testCase => testCase.Returns(true)); - - public static IEnumerable notIntersectedRectanglesTestCases = - new[] - { - new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) - .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), - new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) - .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), - }.Select(testCase => testCase.Returns(false)); - - #endregion - - [TestCaseSource(nameof(intersectedRectanglesTestCases))] - [TestCaseSource(nameof(notIntersectedRectanglesTestCases))] + [TestCaseSource(typeof(IntersectedRectanglesTestCases))] + [TestCaseSource(typeof(NotIntersectedRectanglesTestCases))] public bool ReturnValue(Rectangle first, Rectangle second) { return new[] { first, second }.HasIntersectedRectangles() @@ -49,9 +16,14 @@ public bool ReturnValue(Rectangle first, Rectangle second) } [Test] - public void ThrowException_ThenEmptyOrConainsOneElement() + public void ThrowException_ThenConainsOneElement() { - Assert.Throws(() => Array.Empty().HasIntersectedRectangles()); Assert.Throws(() => new[] { new Rectangle(0, 0, 1, 1) }.HasIntersectedRectangles()); } + + [Test] + public void ThrowException_ThenEmpty() + { + Assert.Throws(() => Array.Empty().HasIntersectedRectangles()); + } } \ No newline at end of file diff --git a/cs/TagCloudTests/SpiralCloudShaper_Should.cs b/cs/TagCloudTests/SpiralCloudShaper_Should.cs new file mode 100644 index 000000000..529c4f359 --- /dev/null +++ b/cs/TagCloudTests/SpiralCloudShaper_Should.cs @@ -0,0 +1,17 @@ +using System.Drawing; +using System.Runtime.ExceptionServices; +using TagCloud; + +namespace TagCloudTests; + +public class SpiralCloudShaper_Should +{ + [TestCase(-1, 1, TestName = "NegativeCoefficient")] + [TestCase(1, -1, TestName = "NegativeDeltaAngle")] + [TestCase(-1, -1, TestName = "NegativeDeltaAngleAndCoefficient")] + public void Throw_OnCreationWith(double coefficient, double deltaAngle) + { + var center = new Point(0, 0); + Assert.Throws(() => new SpiralCloudShaper(center, coefficient, deltaAngle)); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudDrawer.cs b/cs/TagCloudTests/TagCloudDrawer.cs new file mode 100644 index 000000000..6820b337f --- /dev/null +++ b/cs/TagCloudTests/TagCloudDrawer.cs @@ -0,0 +1,31 @@ +using System.Drawing; + +namespace TagCloud; + +public static class TagCloudDrawer +{ + private const string RelativePathToFailDirectory = @"..\..\..\Fails"; + + public static void Draw(CircularCloudLayouter layouter) + { + var rectangles = layouter.Rectangles; + if (rectangles.Count() == 0) + return; + + var minX = rectangles.Min(rect => rect.X); + var maxX = rectangles.Max(rect => rect.Right); + var minY = rectangles.Min(rect => rect.Top); + var maxY = rectangles.Max(rect => rect.Bottom); + + using var bitmap = new Bitmap(maxX - minX + 2, maxY - minY + 2); + using var graphics = Graphics.FromImage(bitmap); + graphics.DrawRectangles(new Pen(Color.Red, 1), + rectangles.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) + .ToArray()); + + var pathToFile = @$"{RelativePathToFailDirectory}\{TestContext.CurrentContext.Test.FullName}.jpg"; + var absolutePath = Path.GetFullPath(pathToFile); + bitmap.Save(pathToFile); + Console.WriteLine($"Tag cloud visualization saved to file {absolutePath}"); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs new file mode 100644 index 000000000..dd9db6df3 --- /dev/null +++ b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs @@ -0,0 +1,33 @@ +using System.Collections; +using System.Drawing; + +namespace TagCloudTests.TestData; + +public class IntersectedRectanglesTestCases : IEnumerable +{ + private static TestCaseData[] data = new[] + { + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) + .SetName("WhenFirstEqualsSecond"), + new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstInSecond"), + new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByLeftSide"), + new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByRightSide"), + new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByTopSide"), + new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByBottomSide"), + new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) + .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), + new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) + .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") + }; + + public IEnumerator GetEnumerator() + { + foreach (var testCase in data) + yield return testCase.Returns(true); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs new file mode 100644 index 000000000..6293425f5 --- /dev/null +++ b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs @@ -0,0 +1,21 @@ +using System.Collections; +using System.Drawing; + +namespace TagCloudTests.TestData; + +public class NotIntersectedRectanglesTestCases: IEnumerable +{ + private static TestCaseData[] data = new[] + { + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), + new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), + }; + + public IEnumerator GetEnumerator() + { + foreach (var testCase in data) + yield return testCase.Returns(false); + } +} \ No newline at end of file From 0d4d766a0ac4350527c618e1f3abc58e87d2cc97 Mon Sep 17 00:00:00 2001 From: ShpakovDaniel <44341164+Reltig@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:23:06 +0500 Subject: [PATCH 6/6] second fix --- cs/TagCloud/CircularCloudLayouter.cs | 11 +- cs/TagCloud/CloudDrawers/CloudDrawer.cs | 8 ++ cs/TagCloud/CloudDrawers/TagCloudDrawer.cs | 52 ++++++++ .../ColorSelectors/ConstantColorSelector.cs | 15 +++ cs/TagCloud/ColorSelectors/IColorSelector.cs | 8 ++ .../ColorSelectors/RandomColorSelector.cs | 22 ++++ cs/TagCloud/Extensions.cs | 17 +++ cs/TagCloud/ICloudShaper.cs | 2 +- cs/TagCloud/SpiralCloudShaper.cs | 54 ++++---- cs/TagCloud/TagCloud.csproj | 4 + .../CircularCloudLayouterTests.cs | 119 ++++++++++++++++++ cs/TagCloudTests/CloudTests_Should.cs | 93 -------------- ...ularCloudLayouterTests.WithSquareShape.jpg | Bin 0 -> 1863 bytes cs/TagCloudTests/Extensions.cs | 17 ++- .../HasIntersectedRectangleTests.cs | 29 +++++ .../HasIntersectedRectangleTests_Should.cs | 29 ----- cs/TagCloudTests/SpiralCloudShaperTests.cs | 44 +++++++ cs/TagCloudTests/SpiralCloudShaper_Should.cs | 17 --- cs/TagCloudTests/TagCloudDrawer.cs | 31 ----- cs/TagCloudTests/TagCloudDrawerTest.cs | 60 +++++++++ cs/TagCloudTests/TagCloudTests.csproj | 5 + .../IntersectedRectanglesTestCases.cs | 45 ++++--- .../NotIntersectedRectanglesTestCases.cs | 21 ++-- 23 files changed, 461 insertions(+), 242 deletions(-) create mode 100644 cs/TagCloud/CloudDrawers/CloudDrawer.cs create mode 100644 cs/TagCloud/CloudDrawers/TagCloudDrawer.cs create mode 100644 cs/TagCloud/ColorSelectors/ConstantColorSelector.cs create mode 100644 cs/TagCloud/ColorSelectors/IColorSelector.cs create mode 100644 cs/TagCloud/ColorSelectors/RandomColorSelector.cs create mode 100644 cs/TagCloud/Extensions.cs create mode 100644 cs/TagCloudTests/CircularCloudLayouterTests.cs delete mode 100644 cs/TagCloudTests/CloudTests_Should.cs create mode 100644 cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg create mode 100644 cs/TagCloudTests/HasIntersectedRectangleTests.cs delete mode 100644 cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs create mode 100644 cs/TagCloudTests/SpiralCloudShaperTests.cs delete mode 100644 cs/TagCloudTests/SpiralCloudShaper_Should.cs delete mode 100644 cs/TagCloudTests/TagCloudDrawer.cs create mode 100644 cs/TagCloudTests/TagCloudDrawerTest.cs diff --git a/cs/TagCloud/CircularCloudLayouter.cs b/cs/TagCloud/CircularCloudLayouter.cs index 193369fb9..6974030ac 100644 --- a/cs/TagCloud/CircularCloudLayouter.cs +++ b/cs/TagCloud/CircularCloudLayouter.cs @@ -14,7 +14,7 @@ public CircularCloudLayouter(Point center) { this.center = center; rectangles = new List(); - shaper = new SpiralCloudShaper(this.center); + shaper = SpiralCloudShaper.Create(this.center); } public Rectangle PutNextRectangle(Size size) @@ -24,13 +24,14 @@ public Rectangle PutNextRectangle(Size size) if (size.Height <= 0) throw new ArgumentException("Size height must be positive number"); - var point = shaper.GetNextPossiblePoint(); - var rectangle = new Rectangle(point, size); - while (Rectangles.Any(rect => rect.IntersectsWith(rectangle))) + Rectangle rectangle = Rectangle.Empty; + foreach (var point in shaper.GetPossiblePoints()) { - point = shaper.GetNextPossiblePoint(); rectangle = new Rectangle(point, size); + if (!Rectangles.Any(rect => rect.IntersectsWith(rectangle))) + break; } + rectangles.Add(rectangle); return rectangle; } diff --git a/cs/TagCloud/CloudDrawers/CloudDrawer.cs b/cs/TagCloud/CloudDrawers/CloudDrawer.cs new file mode 100644 index 000000000..d9673f5b4 --- /dev/null +++ b/cs/TagCloud/CloudDrawers/CloudDrawer.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloudTests; + +public interface ICloudDrawer +{ + void Draw(IEnumerable rectangle, string name); +} \ No newline at end of file diff --git a/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs b/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs new file mode 100644 index 000000000..493c9874a --- /dev/null +++ b/cs/TagCloud/CloudDrawers/TagCloudDrawer.cs @@ -0,0 +1,52 @@ +using System.Drawing; +using System.Dynamic; +using TagCloudTests; + +namespace TagCloud; + +public class TagCloudDrawer : ICloudDrawer +{ + private readonly string path; + private readonly IColorSelector selector; + + private TagCloudDrawer(string path, IColorSelector selector) + { + this.path = path; + this.selector = selector; + } + + public void Draw(IEnumerable rectangles, string name) + { + if (rectangles.Count() == 0) + throw new ArgumentException("Empty rectangles list"); + + var minX = rectangles.Min(rect => rect.X); + var maxX = rectangles.Max(rect => rect.Right); + var minY = rectangles.Min(rect => rect.Top); + var maxY = rectangles.Max(rect => rect.Bottom); + + using var bitmap = new Bitmap(maxX - minX + 2, maxY - minY + 2); + using var graphics = Graphics.FromImage(bitmap); + graphics.DrawRectangles( + selector, + rectangles + .Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) + .ToArray() + ); + SaveToFile(bitmap, name); + } + + private void SaveToFile(Bitmap bitmap, string name) + { + var pathToFile = @$"{path}\{name}.jpg"; + bitmap.Save(pathToFile); + Console.WriteLine($"Tag cloud visualization saved to file {path}"); + } + + public static TagCloudDrawer Create(string path, IColorSelector selector) + { + if (!Directory.Exists(path)) + throw new ArgumentException("Directory does not exist"); + return new TagCloudDrawer(path, selector); + } +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs b/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs new file mode 100644 index 000000000..35a1ff03a --- /dev/null +++ b/cs/TagCloud/ColorSelectors/ConstantColorSelector.cs @@ -0,0 +1,15 @@ +using System.Drawing; +using TagCloudTests; + +namespace TagCloud; + +public class ConstantColorSelector : IColorSelector +{ + private Color color; + public ConstantColorSelector(Color color) + { + this.color = color; + } + + public Color PickColor() => color; +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/IColorSelector.cs b/cs/TagCloud/ColorSelectors/IColorSelector.cs new file mode 100644 index 000000000..324d62365 --- /dev/null +++ b/cs/TagCloud/ColorSelectors/IColorSelector.cs @@ -0,0 +1,8 @@ +using System.Drawing; + +namespace TagCloudTests; + +public interface IColorSelector +{ + Color PickColor(); +} \ No newline at end of file diff --git a/cs/TagCloud/ColorSelectors/RandomColorSelector.cs b/cs/TagCloud/ColorSelectors/RandomColorSelector.cs new file mode 100644 index 000000000..0afb3dcff --- /dev/null +++ b/cs/TagCloud/ColorSelectors/RandomColorSelector.cs @@ -0,0 +1,22 @@ +using System.Drawing; + +namespace TagCloudTests; + +public class RandomColorSelector : IColorSelector +{ + private readonly Random random; + + public RandomColorSelector() + { + random = new Random(DateTime.Now.Microsecond); + } + + public Color PickColor() + { + return Color.FromArgb(255, + random.Next(0, 255), + random.Next(0, 255), + random.Next(0, 255) + ); + } +} \ No newline at end of file diff --git a/cs/TagCloud/Extensions.cs b/cs/TagCloud/Extensions.cs new file mode 100644 index 000000000..02258548d --- /dev/null +++ b/cs/TagCloud/Extensions.cs @@ -0,0 +1,17 @@ +using System.Drawing; +using TagCloudTests; + +namespace TagCloud; + +public static class Extensions +{ + public static void DrawRectangles(this Graphics graphics, IColorSelector selector, Rectangle[] rectangles, int lineWidth = 1) + { + var pen = new Pen(selector.PickColor(), lineWidth); + foreach (var rectangle in rectangles) + { + graphics.DrawRectangle(pen, rectangle); + pen.Color = selector.PickColor(); + } + } +} \ No newline at end of file diff --git a/cs/TagCloud/ICloudShaper.cs b/cs/TagCloud/ICloudShaper.cs index 24270e26d..67c8e063e 100644 --- a/cs/TagCloud/ICloudShaper.cs +++ b/cs/TagCloud/ICloudShaper.cs @@ -4,5 +4,5 @@ namespace TagCloud; public interface ICloudShaper { - Point GetNextPossiblePoint(); + IEnumerable GetPossiblePoints(); } \ No newline at end of file diff --git a/cs/TagCloud/SpiralCloudShaper.cs b/cs/TagCloud/SpiralCloudShaper.cs index fb963c892..6a27f1876 100644 --- a/cs/TagCloud/SpiralCloudShaper.cs +++ b/cs/TagCloud/SpiralCloudShaper.cs @@ -5,45 +5,49 @@ namespace TagCloud; public class SpiralCloudShaper : ICloudShaper { private Point center; - private Point previousPoint; - - private double currentAngle; private double coefficient; private double deltaAngle; - - public double Radius => coefficient * currentAngle; - public SpiralCloudShaper(Point center, double coefficient = 1, double deltaAngle = 0.1) + private SpiralCloudShaper(Point center, double coefficient, double deltaAngle) { this.center = center; - if (coefficient <= 0) - throw new ArgumentException("Spiral coefficient must be positive number"); - this.coefficient = coefficient; - - if (deltaAngle <= 0) - throw new ArgumentException("Spiral delta angle must be positive number"); this.deltaAngle = deltaAngle; - - currentAngle = 0; - previousPoint = center; + this.coefficient = coefficient; } - public Point GetNextPossiblePoint() + + public IEnumerable GetPossiblePoints() { - currentAngle += deltaAngle; - var position = CalculatePointByCurrentAngle(); - while (position == previousPoint) + var currentAngle = 0D; + var position = center; + var previousPoint = position; + while(true) { - currentAngle += deltaAngle; - position = CalculatePointByCurrentAngle(); + while (position == previousPoint) + { + currentAngle += deltaAngle; + previousPoint = position; + position = CalculatePointByCurrentAngle(currentAngle); + } + yield return position; + previousPoint = position; + position = CalculatePointByCurrentAngle(currentAngle); } - return position; } - private Point CalculatePointByCurrentAngle() + private Point CalculatePointByCurrentAngle(double angle) { return new Point( - center.X + (int)(Radius * Math.Cos(currentAngle)), - center.Y + (int)(Radius * Math.Sin(currentAngle)) + center.X + (int)(coefficient * angle * Math.Cos(angle)), + center.Y + (int)(coefficient * angle * Math.Sin(angle)) ); } + + public static SpiralCloudShaper Create(Point center, double coefficient = 0.1, double deltaAngle = 0.1) + { + if (coefficient <= 0) + throw new ArgumentException("Spiral coefficient must be positive number"); + if (deltaAngle <= 0) + throw new ArgumentException("Spiral delta angle must be positive number"); + return new SpiralCloudShaper(center, coefficient, deltaAngle); + } } \ No newline at end of file diff --git a/cs/TagCloud/TagCloud.csproj b/cs/TagCloud/TagCloud.csproj index 6836c6808..e298c8464 100644 --- a/cs/TagCloud/TagCloud.csproj +++ b/cs/TagCloud/TagCloud.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/cs/TagCloudTests/CircularCloudLayouterTests.cs b/cs/TagCloudTests/CircularCloudLayouterTests.cs new file mode 100644 index 000000000..b402ad336 --- /dev/null +++ b/cs/TagCloudTests/CircularCloudLayouterTests.cs @@ -0,0 +1,119 @@ +using System.Drawing; +using FluentAssertions; +using NUnit.Framework.Interfaces; +using TagCloud; + +namespace TagCloudTests; + +public class CircularCloudLayouterTests +{ + private const string RelativePathToFailDirectory = @"..\..\..\Fails"; + + private CircularCloudLayouter layouter; + private Point center; + private ICloudDrawer drawer; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + drawer = TagCloudDrawer.Create(Path.GetFullPath(RelativePathToFailDirectory), new RandomColorSelector()); + } + + [SetUp] + public void Setup() + { + center = new Point(0, 0); + layouter = new CircularCloudLayouter(center); + } + + [TearDown] + public void Tear_Down() + { + if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) + drawer.Draw(layouter.Rectangles, TestContext.CurrentContext.Test.FullName); + } + + [Test] + public void ReturnEmptyList_WhenCreated() + { + layouter.Rectangles.Should().BeEmpty(); + } + + [Test] + public void ReturnOneElementList_WhenAddOne() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(1); + } + + [Test] + public void ReturnTwoElementList_WhenAddTwo() + { + layouter.PutNextRectangle(new Size(1, 1)); + layouter.PutNextRectangle(new Size(1, 1)); + layouter.Rectangles.Count().Should().Be(2); + NotIntersectedAssert(layouter.Rectangles); + } + + [TestCase(1, 1, 500, 0.77D, TestName = "WithSquareShape")] + [TestCase(20, 10, 500, 0.67D, TestName = "WithRectangleShape")] + public void AddManyNotIntersectedRectangles_WithConstantSize_ByCloudShape(int width, int height, int count, double accuracy) + { + var size = new Size(width, height); + for (int i = 0; i < count; i++) + layouter.PutNextRectangle(size); + + layouter.Rectangles.Count().Should().Be(count); + NotIntersectedAssert(layouter.Rectangles); + CircleShapeAssertion(layouter.Rectangles, accuracy); + } + + [Test] + public void AddManyNotIntersectedRectangles_WithVariableSize_ByCloudShape() + { + var rnd = new Random(DateTime.Now.Microsecond); + + for (int i = 0; i < 200; i++) + { + var size = new Size(rnd.Next(20, 40), rnd.Next(20, 40)); + layouter.PutNextRectangle(size); + } + + layouter.Rectangles.Count().Should().Be(200); + NotIntersectedAssert(layouter.Rectangles); + CircleShapeAssertion(layouter.Rectangles, 0.6D); + } + + [TestCase(-1, 1, TestName = "WithNegativeWidth")] + [TestCase(1, -1, TestName = "WithNegativeHeight")] + [TestCase(-1, -1, TestName = "WithNegativeWidthAndHeight")] + public void Throw_ThenTryPutRectangle(int width, int height) + { + Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); + } + + private void CircleShapeAssertion(IEnumerable rectangles, double accuracy) + { + var circleRadius = rectangles + .SelectMany(rect => new[] + { + rect.Location, + rect.Location with { X = rect.Right }, + rect.Location with { Y = rect.Bottom }, + rect.Location with { X = rect.Right, Y = rect.Bottom } + }) + .Max(point => point.GetDistanceTo(center)); + Console.WriteLine(circleRadius); + var containingCircleRadius = Math.PI * circleRadius * circleRadius; + var rectanglesTotalArea = rectangles.Sum(rect => rect.Width * rect.Height); + Assert.GreaterOrEqual(rectanglesTotalArea / containingCircleRadius, accuracy); + } + + public static void NotIntersectedAssert(IEnumerable rectangles) + { + rectangles + .HasIntersectedRectangles() + .Should() + .BeFalse(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/CloudTests_Should.cs b/cs/TagCloudTests/CloudTests_Should.cs deleted file mode 100644 index 4de13e63a..000000000 --- a/cs/TagCloudTests/CloudTests_Should.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Drawing; -using FluentAssertions; -using NUnit.Framework.Interfaces; -using TagCloud; - -namespace TagCloudTests; - -public class CloudTests_Should -{ - private CircularCloudLayouter layouter; - - [SetUp] - public void Setup() - { - var center = new Point(0, 0); - layouter = new CircularCloudLayouter(center); - } - - [TearDown] - public void Tear_Down() - { - if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed) - TagCloudDrawer.Draw(layouter); - } - - [Test] - public void ReturnEmptyList_WhenCreated() - { - layouter.Rectangles.Should().BeEmpty(); - } - - [Test] - public void ReturnOneElementList_WhenAddOne() - { - layouter.PutNextRectangle(new Size(1, 1)); - layouter.Rectangles.Count().Should().Be(1); - } - - [Test] - public void ReturnTwoElementList_WhenAddTwo() - { - layouter.PutNextRectangle(new Size(1, 1)); - layouter.PutNextRectangle(new Size(1, 1)); - layouter.Rectangles.Count().Should().Be(2); - NotIntersectedAssertation(); - } - - [TestCase(1, 1, 100, TestName = "WithSquareShape")] - [TestCase(20, 10, 100, TestName = "WithRectangleShape")] - public void AddManyRectangles_WithConstantSize(int width, int height, int count) - { - var size = new Size(width, height); - for (int i = 0; i < count; i++) - layouter.PutNextRectangle(size); - - layouter.Rectangles.Count().Should().Be(count); - NotIntersectedAssertation(); - } - - [TestCase(5, 10, 2, 4, 100)] - [TestCase(20, 40, 20, 40, 200)] - public void AddManyRectangles_WithVariableSize(int widthMin, int widthMax, int heightMin, int heightMax, int count) - { - var rnd = new Random(DateTime.Now.Microsecond); - - for (int i = 0; i < count; i++) - { - var size = new Size(rnd.Next(widthMin, widthMax), rnd.Next(heightMin, heightMax)); - layouter.PutNextRectangle(size); - } - - layouter.Rectangles.Count().Should().Be(count); - NotIntersectedAssertation(); - } - - [TestCase(-1, 1, TestName = "WithNegativeWidth")] - [TestCase(1, -1, TestName = "WithNegativeHeight")] - [TestCase(-1, -1, TestName = "WithNegativeWidthAndHeight")] - public void Throw_ThenTryPutRectangle(int width, int height) - { - Assert.Throws(() => layouter.PutNextRectangle(new Size(width, height))); - } - - public void NotIntersectedAssertation() - { - layouter - .Rectangles - .HasIntersectedRectangles() - .Should() - .BeFalse(); - } - -} \ No newline at end of file diff --git a/cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg b/cs/TagCloudTests/Examples/TagCloudTests.CircularCloudLayouterTests.WithSquareShape.jpg new file mode 100644 index 0000000000000000000000000000000000000000..82d7b5c7925c57d973a2466791e28d06380db295 GIT binary patch literal 1863 zcmV-N2e|l&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGluK)lMuL1Y{ON0Ob2HZ(RK~zXf&C`2a z&-Vhy@n4gv$h{HKMY;Sc#&DEYi7rbtkyhc14C$g@Icjc^%c?_2GPlsmEx9fyg;CD0 z!mrO4sb78b zC0-cMI{!$K1WL`@T~A^=L($791;z&g@i2dgvFa!{ANaHayEJThbF}*vUH%nZow%O; zx047T^j!2wF`qO8)U>i`o`dUgzxZ`t4%b_#>FkjIt_ zEF`#Aq&~j&j$}+j0;QayhVquhdA`u!%vJvs?C;B%`|f@2~ty_@H;I}KNdi)0 zNJUB~ZQv}v?deHP)D{X%dtukPfuJ#WUk_HE z&P9Jv6lc8ZDS5mQKi_3IrPLDrz>JM^X7I>l65lt*5Ug_K7?A!8INEHOGl=;i)gJKq&$rXK;pK?J&t zW0`Ip8%!No=^cxO_7kq1y@u)jB-Qs*DfWb1C&du$h=ol!AP7GpSUxKCgcyn;pl z0ak9P!pVCL?RUS8&XT@(Tcw~CHsh(OlXl8t*5tRR=;P-^l<5mFKTRgUyyD5k z?}_C{i-njCu@qpJdr?gNE`W%?JQWG>&xhtAb+t5$9vY{y=&7KaRRSTCR0J&cLU!l_ z>LUM2WymwiG+(0G!;}2@92)M+QA|Eg{Wv>%UbM!zt&GvV1`_aK6Hh9>!L3sfQ^E%! z``eP%V0Po@NL7`pusdWaK+8HCw3^(>y0;g%n`Z=Av$LFor#GQoYDKU?8HQ13ahu+O z9~M{9W>+`P_ex~l){18QBFaN|1UPnc2#?dh}UKfQ%ifny|VN7P^$avHbf8|dAtA=adUzCO;JsUOBk`Fn&F++bf^PrR&0isZh= zd~(Q_qk&p@4Ij$lh5)J!^r`*q1W)>}6{(qD)&i=m2ckbciU;!_v#p&gm&Qe6zo7?H zH^szIH2?Hrhl!l0!_>MJgj>$Q(=3+W z&EN3pH;p3o@vok3bp@dsMWTWxCClAw>8i7td_^t>DsL7gS(7Up%D#pZY@9U}mv5^@ z!M)8e_in?A1PAiI{1KVnHqozw(ptd7l`}bcB7;;L9f}$|;ya}j+55d|@3Vumm+s)y z_EU7jGenDGtkHUy%cm+8-6I1<0)5j-nAEC*N1}<>f8cI(oXBTc9jiVV!js$mn7t#J zn@c}rxZ5nw-H#Xj4E`@`0bSeJik9b$BGc0ud8MXkR+c__A32JS7S9u)`qkgJ7Epe+ z`8Q~8bC8_j|Ij0P;k8zv`td(*uL9M#{$D4P{S(@{gf^FMs> CartesianProduct(this IEnumerable s public static bool HasIntersectedRectangles(this IEnumerable rectangles) { - foreach (var (first, second) in rectangles.CartesianProduct()) - if (first.IntersectsWith(second)) - return true; + return rectangles + .SelectMany( + (x, i) => rectangles.Skip(i + 1), + (x, y) => Tuple.Create(x, y) + ) + .Any(tuple => tuple.Item1.IntersectsWith(tuple.Item2)); + } - return false; + public static double GetDistanceTo(this Point first, Point second) + { + return Math.Sqrt((first.X - second.X) * (first.X - second.X) + (first.Y - second.Y) * (first.Y - second.Y)); } } \ No newline at end of file diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests.cs b/cs/TagCloudTests/HasIntersectedRectangleTests.cs new file mode 100644 index 000000000..7eb4627c8 --- /dev/null +++ b/cs/TagCloudTests/HasIntersectedRectangleTests.cs @@ -0,0 +1,29 @@ +using System.Drawing; +using FluentAssertions; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using TagCloudTests.TestData; + +namespace TagCloudTests; + +public class HasIntersectedRectangleTests +{ + [TestCaseSource(typeof(IntersectedRectanglesTestCases), nameof(IntersectedRectanglesTestCases.Data))] + [TestCaseSource(typeof(NotIntersectedRectanglesTestCases), nameof(NotIntersectedRectanglesTestCases.Data))] + public bool ReturnValue(Rectangle first, Rectangle second) + { + return new[] { first, second }.HasIntersectedRectangles() + && new[] { second, first }.HasIntersectedRectangles(); + } + + [Test] + public void ReturnFalse_ThenConainsOneElement() + { + new[] { new Rectangle(0, 0, 1, 1) }.HasIntersectedRectangles().Should().BeFalse(); + } + + [Test] + public void ReturnFalse_ThenEmpty() + { + Array.Empty().HasIntersectedRectangles().Should().BeFalse(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs b/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs deleted file mode 100644 index 0e5b95678..000000000 --- a/cs/TagCloudTests/HasIntersectedRectangleTests_Should.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Drawing; -using FluentAssertions; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using TagCloudTests.TestData; - -namespace TagCloudTests; - -public class HasIntersectedRectangleTests_Should -{ - [TestCaseSource(typeof(IntersectedRectanglesTestCases))] - [TestCaseSource(typeof(NotIntersectedRectanglesTestCases))] - public bool ReturnValue(Rectangle first, Rectangle second) - { - return new[] { first, second }.HasIntersectedRectangles() - && new[] { second, first }.HasIntersectedRectangles(); - } - - [Test] - public void ThrowException_ThenConainsOneElement() - { - Assert.Throws(() => new[] { new Rectangle(0, 0, 1, 1) }.HasIntersectedRectangles()); - } - - [Test] - public void ThrowException_ThenEmpty() - { - Assert.Throws(() => Array.Empty().HasIntersectedRectangles()); - } -} \ No newline at end of file diff --git a/cs/TagCloudTests/SpiralCloudShaperTests.cs b/cs/TagCloudTests/SpiralCloudShaperTests.cs new file mode 100644 index 000000000..0d7b1492a --- /dev/null +++ b/cs/TagCloudTests/SpiralCloudShaperTests.cs @@ -0,0 +1,44 @@ +using System.Drawing; +using System.Runtime.ExceptionServices; +using FluentAssertions; +using TagCloud; + +namespace TagCloudTests; + +public class SpiralCloudShaperTests +{ + private Point center; + private SpiralCloudShaper shaper; + + [SetUp] + public void SetUp() + { + center = new Point(0, 0); + shaper = SpiralCloudShaper.Create(center); + } + + [TestCase(-1, 1, TestName = "NegativeCoefficient")] + [TestCase(1, -1, TestName = "NegativeDeltaAngle")] + [TestCase(-1, -1, TestName = "NegativeDeltaAngleAndCoefficient")] + public void Throw_OnCreationWith(double coefficient, double deltaAngle) + { + Assert.Throws(() => SpiralCloudShaper.Create(center, coefficient, deltaAngle)); + } + + [TestCase(1, 1, TestName = "Integer coefficients")] + [TestCase(0.1D, 0.1D, TestName = "Double coefficients")] + public void NotThrow_OnCreationWith(double coefficient, double deltaAngle) + { + Assert.DoesNotThrow(() => SpiralCloudShaper.Create(center, coefficient, deltaAngle)); + } + + [Test] + public void RadiusShouldIncrease() + { + shaper.GetPossiblePoints() + .Take(100) + .Select(point => point.GetDistanceTo(center)) + .Should() + .BeInAscendingOrder(); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/SpiralCloudShaper_Should.cs b/cs/TagCloudTests/SpiralCloudShaper_Should.cs deleted file mode 100644 index 529c4f359..000000000 --- a/cs/TagCloudTests/SpiralCloudShaper_Should.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Drawing; -using System.Runtime.ExceptionServices; -using TagCloud; - -namespace TagCloudTests; - -public class SpiralCloudShaper_Should -{ - [TestCase(-1, 1, TestName = "NegativeCoefficient")] - [TestCase(1, -1, TestName = "NegativeDeltaAngle")] - [TestCase(-1, -1, TestName = "NegativeDeltaAngleAndCoefficient")] - public void Throw_OnCreationWith(double coefficient, double deltaAngle) - { - var center = new Point(0, 0); - Assert.Throws(() => new SpiralCloudShaper(center, coefficient, deltaAngle)); - } -} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudDrawer.cs b/cs/TagCloudTests/TagCloudDrawer.cs deleted file mode 100644 index 6820b337f..000000000 --- a/cs/TagCloudTests/TagCloudDrawer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Drawing; - -namespace TagCloud; - -public static class TagCloudDrawer -{ - private const string RelativePathToFailDirectory = @"..\..\..\Fails"; - - public static void Draw(CircularCloudLayouter layouter) - { - var rectangles = layouter.Rectangles; - if (rectangles.Count() == 0) - return; - - var minX = rectangles.Min(rect => rect.X); - var maxX = rectangles.Max(rect => rect.Right); - var minY = rectangles.Min(rect => rect.Top); - var maxY = rectangles.Max(rect => rect.Bottom); - - using var bitmap = new Bitmap(maxX - minX + 2, maxY - minY + 2); - using var graphics = Graphics.FromImage(bitmap); - graphics.DrawRectangles(new Pen(Color.Red, 1), - rectangles.Select(rect => rect with { X = -minX + rect.X, Y = -minY + rect.Y }) - .ToArray()); - - var pathToFile = @$"{RelativePathToFailDirectory}\{TestContext.CurrentContext.Test.FullName}.jpg"; - var absolutePath = Path.GetFullPath(pathToFile); - bitmap.Save(pathToFile); - Console.WriteLine($"Tag cloud visualization saved to file {absolutePath}"); - } -} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudDrawerTest.cs b/cs/TagCloudTests/TagCloudDrawerTest.cs new file mode 100644 index 000000000..d83333bb9 --- /dev/null +++ b/cs/TagCloudTests/TagCloudDrawerTest.cs @@ -0,0 +1,60 @@ +using System.Drawing; +using FluentAssertions; +using TagCloud; + +namespace TagCloudTests; + +public class TagCloudDrawerTest +{ + private const string RelativePathToTestDirectory = @"..\..\..\Test"; + + private static readonly string path = Path.GetFullPath(RelativePathToTestDirectory); + private readonly DirectoryInfo directory = new DirectoryInfo(path); + + private TagCloudDrawer drawer; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + foreach (var file in directory.EnumerateFiles()) + file.Delete(); + } + + [SetUp] + public void SetUp() + { + drawer = TagCloudDrawer.Create(path, new ConstantColorSelector(Color.Black)); + } + + [Test] + public void Throw_ThenDrawEmptyList() + { + Assert.Throws(() => drawer.Draw(Array.Empty(), "throw")); + } + + [Test] + public void Throw_ThenDirectoryDoesNotExist() + { + Assert.Throws(() => TagCloudDrawer.Create( + $@"{path}\DontExist", + new ConstantColorSelector(Color.Black)) + ); + } + + [Test] + public void DrawOneCase() + { + var rectangles = new Rectangle[] + { + new Rectangle(0, 1, 2, 3), + new Rectangle(4, 5, 6, 7), + new Rectangle(8, 9, 10, 11), + }; + drawer.Draw(rectangles, "three_rectangles"); + directory + .EnumerateFiles() + .Count(file => file.Name.Contains("three_rectangles")) + .Should() + .Be(1); + } +} \ No newline at end of file diff --git a/cs/TagCloudTests/TagCloudTests.csproj b/cs/TagCloudTests/TagCloudTests.csproj index ca3cf002e..61f237b8b 100644 --- a/cs/TagCloudTests/TagCloudTests.csproj +++ b/cs/TagCloudTests/TagCloudTests.csproj @@ -27,4 +27,9 @@ + + + + + diff --git a/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs index dd9db6df3..a96784f8b 100644 --- a/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs +++ b/cs/TagCloudTests/TestData/IntersectedRectanglesTestCases.cs @@ -3,31 +3,28 @@ namespace TagCloudTests.TestData; -public class IntersectedRectanglesTestCases : IEnumerable +public class IntersectedRectanglesTestCases { - private static TestCaseData[] data = new[] + public static IEnumerable Data { - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) - .SetName("WhenFirstEqualsSecond"), - new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstInSecond"), - new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByLeftSide"), - new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByRightSide"), - new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByTopSide"), - new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) - .SetName("WhenFirstEnterInSecond_ByBottomSide"), - new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) - .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle"), - new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) - .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle") - }; - - public IEnumerator GetEnumerator() - { - foreach (var testCase in data) - yield return testCase.Returns(true); + get + { + yield return new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(1, 1, 1, 1)) + .SetName("WhenFirstEqualsSecond").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 1, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstInSecond").Returns(true); + yield return new TestCaseData(new Rectangle(-1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByLeftSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 2, 1), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByRightSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, 1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByTopSide").Returns(true); + yield return new TestCaseData(new Rectangle(1, -1, 1, 2), new Rectangle(0, 0, 3, 3)) + .SetName("WhenFirstEnterInSecond_ByBottomSide").Returns(true); + yield return new TestCaseData(new Rectangle(0, 0, 2, 2), new Rectangle(1, 1, 2, 2)) + .SetName("WhenFirstRightTopAngle_IntersectWithSecondLeftBottomAngle").Returns(true); + yield return new TestCaseData(new Rectangle(0, 1, 2, 2), new Rectangle(0, 1, 2, 2)) + .SetName("WhenFirstRightBottomAngle_IntersectWithSecondLeftTopAngle").Returns(true); + } } } \ No newline at end of file diff --git a/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs index 6293425f5..c11563999 100644 --- a/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs +++ b/cs/TagCloudTests/TestData/NotIntersectedRectanglesTestCases.cs @@ -3,19 +3,16 @@ namespace TagCloudTests.TestData; -public class NotIntersectedRectanglesTestCases: IEnumerable +public class NotIntersectedRectanglesTestCases { - private static TestCaseData[] data = new[] + public static IEnumerable Data { - new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) - .SetName("WhenFirstRightBorder_СoincidesWithSecondLeftBorder"), - new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) - .SetName("WhenFirstTopBorder_СoincidesWithSecondBottomBorder"), - }; - - public IEnumerator GetEnumerator() - { - foreach (var testCase in data) - yield return testCase.Returns(false); + get + { + yield return new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(1, 0, 1, 1)) + .SetName("WhenFirstRightBorder_СoncidesWithSecondLeftBorder").Returns(false); + yield return new TestCaseData(new Rectangle(0, 0, 1, 1), new Rectangle(0, 1, 1, 1)) + .SetName("WhenFirstTopBorder_СoncidesWithSecondBottomBorder").Returns(false); + } } } \ No newline at end of file