From a446c37a99fe1a6f02496565ae28882333bfdde4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 03:04:55 +0000 Subject: [PATCH 1/7] Initial plan for issue From 9f59a10f340811323e0264bd345aab369acbba2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 03:08:39 +0000 Subject: [PATCH 2/7] Create DynamicShape2 page with unicode-based angles and line flipping Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- WebAssemblyApp/Layout/NavMenu.razor | 5 + WebAssemblyApp/Pages/DynamicShape2.razor | 141 +++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 WebAssemblyApp/Pages/DynamicShape2.razor diff --git a/WebAssemblyApp/Layout/NavMenu.razor b/WebAssemblyApp/Layout/NavMenu.razor index e200d9c..71070b1 100644 --- a/WebAssemblyApp/Layout/NavMenu.razor +++ b/WebAssemblyApp/Layout/NavMenu.razor @@ -30,6 +30,11 @@ Dynamic Shape + diff --git a/WebAssemblyApp/Pages/DynamicShape2.razor b/WebAssemblyApp/Pages/DynamicShape2.razor new file mode 100644 index 0000000..9cfe376 --- /dev/null +++ b/WebAssemblyApp/Pages/DynamicShape2.razor @@ -0,0 +1,141 @@ +@page "/dynamic-shape-2" + +Dynamic Shape 2 + +

Dynamic Shape 2

+ +
+
+ + +
+ +
+ + @foreach (var line in shapeLines) + { + + } + +
+ + @if (!string.IsNullOrEmpty(inputText)) + { +
+ + Generated @shapeLines.Count lines from "@inputText" + (Unicode values: @string.Join(", ", inputText.Select(c => ((int)c).ToString()))) + +
+ } +
+ +@code { + private string inputText = ""; + private List shapeLines = new List(); + + protected override void OnInitialized() + { + UpdateShape(); + } + + private void OnTextChanged(ChangeEventArgs e) + { + inputText = e.Value?.ToString() ?? ""; + UpdateShape(); + } + + private void UpdateShape() + { + shapeLines.Clear(); + + if (string.IsNullOrEmpty(inputText)) + return; + + double centerX = 250; + double centerY = 200; + + // Starting position for the first line + double currentX = centerX; + double currentY = centerY; + + for (int i = 0; i < inputText.Length; i++) + { + char c = inputText[i]; + int unicode = (int)c; + + // Use unicode value directly as angle (in degrees) + double angle = unicode; + double length = (unicode % 80) + 20; // Line length between 20-99 + + // Calculate line end position from current position + double lineAngle = angle * Math.PI / 180; + double proposedEndX = currentX + Math.Cos(lineAngle) * length; + double proposedEndY = currentY + Math.Sin(lineAngle) * length; + + // Check if line points away from both center lines + bool pointsAwayFromVerticalCenter = Math.Abs(proposedEndX - centerX) > Math.Abs(currentX - centerX); + bool pointsAwayFromHorizontalCenter = Math.Abs(proposedEndY - centerY) > Math.Abs(currentY - centerY); + + // Flip the line if it points away from both center lines + if (pointsAwayFromVerticalCenter && pointsAwayFromHorizontalCenter) + { + angle += 180; + lineAngle = angle * Math.PI / 180; + proposedEndX = currentX + Math.Cos(lineAngle) * length; + proposedEndY = currentY + Math.Sin(lineAngle) * length; + } + + // Ensure the line fits within the SVG bounds (500x400) + double endX = proposedEndX; + double endY = proposedEndY; + + // Clamp the end position to stay within bounds with some margin + double margin = 10; + if (endX < margin) endX = margin; + if (endX > 500 - margin) endX = 500 - margin; + if (endY < margin) endY = margin; + if (endY > 400 - margin) endY = 400 - margin; + + // Generate color based on unicode value + string color = GenerateColor(unicode); + int width = (unicode % 3) + 1; // Line width between 1-3 + + shapeLines.Add(new ShapeLine + { + X1 = currentX, + Y1 = currentY, + X2 = endX, + Y2 = endY, + Color = color, + Width = width + }); + + // Update current position to the end of this line for the next line to connect + currentX = endX; + currentY = endY; + } + } + + private string GenerateColor(int unicode) + { + // Generate color based on unicode value + int r = (unicode * 7) % 256; + int g = (unicode * 13) % 256; + int b = (unicode * 19) % 256; + + return $"rgb({r},{g},{b})"; + } + + private class ShapeLine + { + public double X1 { get; set; } + public double Y1 { get; set; } + public double X2 { get; set; } + public double Y2 { get; set; } + public string Color { get; set; } = ""; + public int Width { get; set; } + } +} \ No newline at end of file From 8261d531243da1b80142ab564b519c154afe823f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:20:31 +0000 Subject: [PATCH 3/7] Changes before error encountered Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- DynamicShapeLogic/Class1.cs | 6 ++++++ DynamicShapeLogic/DynamicShapeLogic.csproj | 9 +++++++++ TestAgentRepo.sln | 6 ++++++ 3 files changed, 21 insertions(+) create mode 100644 DynamicShapeLogic/Class1.cs create mode 100644 DynamicShapeLogic/DynamicShapeLogic.csproj diff --git a/DynamicShapeLogic/Class1.cs b/DynamicShapeLogic/Class1.cs new file mode 100644 index 0000000..a850db0 --- /dev/null +++ b/DynamicShapeLogic/Class1.cs @@ -0,0 +1,6 @@ +namespace DynamicShapeLogic; + +public class Class1 +{ + +} diff --git a/DynamicShapeLogic/DynamicShapeLogic.csproj b/DynamicShapeLogic/DynamicShapeLogic.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/DynamicShapeLogic/DynamicShapeLogic.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/TestAgentRepo.sln b/TestAgentRepo.sln index ec57682..add09f0 100644 --- a/TestAgentRepo.sln +++ b/TestAgentRepo.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAssemblyApp", "WebAssemblyApp\WebAssemblyApp.csproj", "{96068AF6-B9F6-4528-A264-81E721660D7A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicShapeLogic", "DynamicShapeLogic\DynamicShapeLogic.csproj", "{49F8A0C6-B08B-4C9C-8C59-734FBB271F05}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {96068AF6-B9F6-4528-A264-81E721660D7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {96068AF6-B9F6-4528-A264-81E721660D7A}.Release|Any CPU.ActiveCfg = Release|Any CPU {96068AF6-B9F6-4528-A264-81E721660D7A}.Release|Any CPU.Build.0 = Release|Any CPU + {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From db5f3d4fc38ef6c3f9316001da5ab2e7a9eeb3e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:26:51 +0000 Subject: [PATCH 4/7] Separate business logic into DynamicShapeLogic project Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- DynamicShapeLogic/Class1.cs | 89 ++++++++++++++++++++++- WebAssemblyApp/Pages/DynamicShape2.razor | 91 +----------------------- WebAssemblyApp/WebAssemblyApp.csproj | 4 ++ 3 files changed, 95 insertions(+), 89 deletions(-) diff --git a/DynamicShapeLogic/Class1.cs b/DynamicShapeLogic/Class1.cs index a850db0..ef4bac5 100644 --- a/DynamicShapeLogic/Class1.cs +++ b/DynamicShapeLogic/Class1.cs @@ -1,6 +1,93 @@ namespace DynamicShapeLogic; -public class Class1 +public class ShapeLine { + public double X1 { get; set; } + public double Y1 { get; set; } + public double X2 { get; set; } + public double Y2 { get; set; } + public string Color { get; set; } = ""; + public int Width { get; set; } +} + +public class DynamicShapeGenerator +{ + private const double CENTER_X = 250; + private const double CENTER_Y = 200; + private const double SVG_WIDTH = 500; + private const double SVG_HEIGHT = 400; + private const double MARGIN = 10; + + public List GenerateShape(string inputText) + { + var shapeLines = new List(); + + if (string.IsNullOrEmpty(inputText)) + return shapeLines; + + double currentX = CENTER_X; + double currentY = CENTER_Y; + + for (int i = 0; i < inputText.Length; i++) + { + char c = inputText[i]; + int unicode = (int)c; + + // Use unicode value directly as angle (in degrees) + double angle = unicode; + double length = (unicode % 80) + 20; // Line length between 20-99 + + // Calculate line end position from current position + double lineAngle = angle * Math.PI / 180; + double proposedEndX = currentX + Math.Cos(lineAngle) * length; + double proposedEndY = currentY + Math.Sin(lineAngle) * length; + + // Check if line points away from both center lines + bool pointsAwayFromVerticalCenter = Math.Abs(proposedEndX - CENTER_X) > Math.Abs(currentX - CENTER_X); + bool pointsAwayFromHorizontalCenter = Math.Abs(proposedEndY - CENTER_Y) > Math.Abs(currentY - CENTER_Y); + + // Flip the line if it points away from both center lines + if (pointsAwayFromVerticalCenter && pointsAwayFromHorizontalCenter) + { + angle += 180; + lineAngle = angle * Math.PI / 180; + proposedEndX = currentX + Math.Cos(lineAngle) * length; + proposedEndY = currentY + Math.Sin(lineAngle) * length; + } + + // Ensure the line fits within the SVG bounds + double endX = Math.Max(MARGIN, Math.Min(SVG_WIDTH - MARGIN, proposedEndX)); + double endY = Math.Max(MARGIN, Math.Min(SVG_HEIGHT - MARGIN, proposedEndY)); + + // Generate color based on unicode value + string color = GenerateColor(unicode); + int width = (unicode % 3) + 1; // Line width between 1-3 + + shapeLines.Add(new ShapeLine + { + X1 = currentX, + Y1 = currentY, + X2 = endX, + Y2 = endY, + Color = color, + Width = width + }); + + // Update current position to the end of this line for the next line to connect + currentX = endX; + currentY = endY; + } + + return shapeLines; + } + private string GenerateColor(int unicode) + { + // Generate color based on unicode value + int r = (unicode * 7) % 256; + int g = (unicode * 13) % 256; + int b = (unicode * 19) % 256; + + return $"rgb({r},{g},{b})"; + } } diff --git a/WebAssemblyApp/Pages/DynamicShape2.razor b/WebAssemblyApp/Pages/DynamicShape2.razor index 9cfe376..7aa5edf 100644 --- a/WebAssemblyApp/Pages/DynamicShape2.razor +++ b/WebAssemblyApp/Pages/DynamicShape2.razor @@ -1,4 +1,5 @@ @page "/dynamic-shape-2" +@using DynamicShapeLogic Dynamic Shape 2 @@ -35,6 +36,7 @@ @code { private string inputText = ""; private List shapeLines = new List(); + private readonly DynamicShapeGenerator shapeGenerator = new DynamicShapeGenerator(); protected override void OnInitialized() { @@ -49,93 +51,6 @@ private void UpdateShape() { - shapeLines.Clear(); - - if (string.IsNullOrEmpty(inputText)) - return; - - double centerX = 250; - double centerY = 200; - - // Starting position for the first line - double currentX = centerX; - double currentY = centerY; - - for (int i = 0; i < inputText.Length; i++) - { - char c = inputText[i]; - int unicode = (int)c; - - // Use unicode value directly as angle (in degrees) - double angle = unicode; - double length = (unicode % 80) + 20; // Line length between 20-99 - - // Calculate line end position from current position - double lineAngle = angle * Math.PI / 180; - double proposedEndX = currentX + Math.Cos(lineAngle) * length; - double proposedEndY = currentY + Math.Sin(lineAngle) * length; - - // Check if line points away from both center lines - bool pointsAwayFromVerticalCenter = Math.Abs(proposedEndX - centerX) > Math.Abs(currentX - centerX); - bool pointsAwayFromHorizontalCenter = Math.Abs(proposedEndY - centerY) > Math.Abs(currentY - centerY); - - // Flip the line if it points away from both center lines - if (pointsAwayFromVerticalCenter && pointsAwayFromHorizontalCenter) - { - angle += 180; - lineAngle = angle * Math.PI / 180; - proposedEndX = currentX + Math.Cos(lineAngle) * length; - proposedEndY = currentY + Math.Sin(lineAngle) * length; - } - - // Ensure the line fits within the SVG bounds (500x400) - double endX = proposedEndX; - double endY = proposedEndY; - - // Clamp the end position to stay within bounds with some margin - double margin = 10; - if (endX < margin) endX = margin; - if (endX > 500 - margin) endX = 500 - margin; - if (endY < margin) endY = margin; - if (endY > 400 - margin) endY = 400 - margin; - - // Generate color based on unicode value - string color = GenerateColor(unicode); - int width = (unicode % 3) + 1; // Line width between 1-3 - - shapeLines.Add(new ShapeLine - { - X1 = currentX, - Y1 = currentY, - X2 = endX, - Y2 = endY, - Color = color, - Width = width - }); - - // Update current position to the end of this line for the next line to connect - currentX = endX; - currentY = endY; - } - } - - private string GenerateColor(int unicode) - { - // Generate color based on unicode value - int r = (unicode * 7) % 256; - int g = (unicode * 13) % 256; - int b = (unicode * 19) % 256; - - return $"rgb({r},{g},{b})"; - } - - private class ShapeLine - { - public double X1 { get; set; } - public double Y1 { get; set; } - public double X2 { get; set; } - public double Y2 { get; set; } - public string Color { get; set; } = ""; - public int Width { get; set; } + shapeLines = shapeGenerator.GenerateShape(inputText); } } \ No newline at end of file diff --git a/WebAssemblyApp/WebAssemblyApp.csproj b/WebAssemblyApp/WebAssemblyApp.csproj index dc83277..261b5d0 100644 --- a/WebAssemblyApp/WebAssemblyApp.csproj +++ b/WebAssemblyApp/WebAssemblyApp.csproj @@ -11,4 +11,8 @@ + + + + From 16da9cee1576e8773f3e8bd9ea898375ee119b01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:39:07 +0000 Subject: [PATCH 5/7] Add Codespace instructions and detailed architecture documentation to README Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- README.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf26b7b..2fe496a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ dotnet run The application will be available at `http://localhost:5110` (or another port if 5110 is in use). -### Project Structure +### Project Structure (Legacy) - `TestAgentRepo.sln` - Visual Studio solution file - `WebAssemblyApp/` - Blazor WebAssembly project @@ -37,6 +37,109 @@ The application will be available at `http://localhost:5110` (or another port if - `wwwroot/` - Static web assets including HTML page - `Pages/` - Razor pages and components - `Program.cs` - Application entry point +- `DynamicShapeLogic/` - Business logic library + - `Class1.cs` - Shape generation algorithms + - `DynamicShapeLogic.csproj` - Project file + +## Running in GitHub Codespaces + +This project is optimized for development in GitHub Codespaces, providing a complete cloud-based development environment. + +### Opening in Codespace + +1. Navigate to the repository on GitHub +2. Click the green "Code" button +3. Select the "Codespaces" tab +4. Click "Create codespace on main" (or your branch) + +### Running in Codespace + +The codespace environment comes pre-configured with .NET 8.0 SDK. To run the application: + +1. **Build the solution:** + ```bash + dotnet build + ``` + +2. **Run the WebAssembly application:** + ```bash + cd WebAssemblyApp + dotnet run + ``` + +3. **Access the application:** + - The application will be available on port 5110 + - Codespaces will automatically forward the port and provide a link + - Look for the "Ports" tab in the terminal panel or notification popup + - Click the generated URL to open the application in your browser + +### Development Tips for Codespaces + +- The terminal is accessible via Terminal → New Terminal +- Port forwarding is automatic for common development ports +- Extensions and settings are automatically configured +- Files are synchronized in real-time + +## Architecture + +This solution follows a layered architecture with clear separation of concerns: + +### Project Structure + +``` +TestAgentRepo/ +├── TestAgentRepo.sln # Visual Studio solution file +├── WebAssemblyApp/ # Blazor WebAssembly UI project +│ ├── Pages/ # Razor pages and components +│ │ ├── Home.razor # Landing page +│ │ ├── DynamicShape.razor # Original dynamic shape generator +│ │ ├── DynamicShape2.razor # Enhanced dynamic shape with line flipping +│ │ ├── DynamicStar.razor # Dynamic star patterns +│ │ └── FivePointStar.razor # Static five-point star +│ ├── Layout/ # Layout components and navigation +│ ├── wwwroot/ # Static web assets +│ └── Program.cs # Application entry point +└── DynamicShapeLogic/ # Business logic library + ├── Class1.cs # Shape generation algorithms + └── DynamicShapeLogic.csproj # Project reference file +``` + +### Key Components + +#### WebAssemblyApp (Presentation Layer) +- **Technology**: Blazor WebAssembly with .NET 8.0 +- **Purpose**: Provides the web-based user interface +- **Features**: + - Interactive SVG shape generation + - Real-time text input processing + - Responsive navigation menu + - Multiple shape generation modes + +#### DynamicShapeLogic (Business Logic Layer) +- **Technology**: .NET 8.0 Class Library +- **Purpose**: Contains the core shape generation algorithms +- **Key Classes**: + - `ShapeLine`: Data model representing individual line segments + - `DynamicShapeGenerator`: Core algorithm for generating connected line patterns + +### Shape Generation Algorithm + +The Dynamic Shape 2 implementation uses a sophisticated algorithm: + +1. **Unicode-based Angles**: Each character's unicode value directly determines line angle +2. **Line Flipping Logic**: Lines that point away from both center axes are automatically flipped +3. **Connected Patterns**: Each line connects to the end point of the previous line +4. **Bounded Drawing**: All shapes are constrained within the SVG canvas boundaries +5. **Dynamic Styling**: Colors and line widths are generated based on unicode values + +### Data Flow + +1. User enters text in the web interface +2. Blazor component passes text to `DynamicShapeGenerator` +3. Generator processes each character's unicode value +4. Algorithm calculates line positions, angles, and styling +5. Generated `ShapeLine` objects are returned to the UI +6. Blazor renders the lines as SVG elements in real-time ### How it Works @@ -44,3 +147,4 @@ The application will be available at `http://localhost:5110` (or another port if 2. The HTML page (`wwwroot/index.html`) loads the Blazor WebAssembly runtime 3. The runtime executes the WebAssembly code in the browser 4. Blazor provides the bridge between WebAssembly and the DOM for interactive web UI +5. The business logic library is referenced and executed within the WebAssembly context From 093389a4fe023053b46806749c0ad7dd695bc76c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:22:35 +0000 Subject: [PATCH 6/7] Fix file name for Class1 and add comprehensive unit tests Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- .../DynamicShapeGeneratorTests.cs | 174 ++++++++++++++++++ .../DynamicShapeLogic.Tests.csproj | 29 +++ DynamicShapeLogic.Tests/GlobalUsings.cs | 1 + .../{Class1.cs => DynamicShapeGenerator.cs} | 0 TestAgentRepo.sln | 6 + 5 files changed, 210 insertions(+) create mode 100644 DynamicShapeLogic.Tests/DynamicShapeGeneratorTests.cs create mode 100644 DynamicShapeLogic.Tests/DynamicShapeLogic.Tests.csproj create mode 100644 DynamicShapeLogic.Tests/GlobalUsings.cs rename DynamicShapeLogic/{Class1.cs => DynamicShapeGenerator.cs} (100%) diff --git a/DynamicShapeLogic.Tests/DynamicShapeGeneratorTests.cs b/DynamicShapeLogic.Tests/DynamicShapeGeneratorTests.cs new file mode 100644 index 0000000..0dfaf89 --- /dev/null +++ b/DynamicShapeLogic.Tests/DynamicShapeGeneratorTests.cs @@ -0,0 +1,174 @@ +namespace DynamicShapeLogic.Tests; + +public class DynamicShapeGeneratorTests +{ + private readonly DynamicShapeGenerator _generator; + + public DynamicShapeGeneratorTests() + { + _generator = new DynamicShapeGenerator(); + } + + [Fact] + public void GenerateShape_WithEmptyInput_ReturnsEmptyList() + { + // Arrange + string emptyInput = ""; + + // Act + var result = _generator.GenerateShape(emptyInput); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void GenerateShape_WithNullInput_ReturnsEmptyList() + { + // Arrange + string? nullInput = null; + + // Act + var result = _generator.GenerateShape(nullInput); + + // Assert + Assert.Empty(result); + } + + [Fact] + public void GenerateShape_WithSingleCharacter_ReturnsOneShapeLine() + { + // Arrange + string input = "A"; + + // Act + var result = _generator.GenerateShape(input); + + // Assert + Assert.Single(result); + + var line = result[0]; + Assert.Equal(250, line.X1); // Should start at center X + Assert.Equal(200, line.Y1); // Should start at center Y + Assert.NotEmpty(line.Color); + Assert.InRange(line.Width, 1, 3); + } + + [Fact] + public void GenerateShape_WithMultipleCharacters_ReturnsConnectedLines() + { + // Arrange + string input = "AB"; + + // Act + var result = _generator.GenerateShape(input); + + // Assert + Assert.Equal(2, result.Count); + + // Second line should start where first line ends + Assert.Equal(result[0].X2, result[1].X1); + Assert.Equal(result[0].Y2, result[1].Y1); + } + + [Fact] + public void GenerateShape_LineStaysWithinBounds() + { + // Arrange + string input = "XYZ"; // Some characters that might generate extreme angles + + // Act + var result = _generator.GenerateShape(input); + + // Assert + foreach (var line in result) + { + Assert.InRange(line.X1, 0, 500); + Assert.InRange(line.Y1, 0, 400); + Assert.InRange(line.X2, 10, 490); // Should respect MARGIN + Assert.InRange(line.Y2, 10, 390); // Should respect MARGIN + } + } + + [Fact] + public void GenerateShape_ColorIsGeneratedCorrectly() + { + // Arrange + string input = "A"; + + // Act + var result = _generator.GenerateShape(input); + + // Assert + var line = result[0]; + Assert.StartsWith("rgb(", line.Color); + Assert.EndsWith(")", line.Color); + Assert.Contains(",", line.Color); + } + + [Fact] + public void GenerateShape_LineWidthIsWithinExpectedRange() + { + // Arrange + string input = "ABCDEF"; + + // Act + var result = _generator.GenerateShape(input); + + // Assert + foreach (var line in result) + { + Assert.InRange(line.Width, 1, 3); + } + } + + [Fact] + public void GenerateShape_DifferentCharactersGenerateDifferentAngles() + { + // Arrange + string input1 = "A"; + string input2 = "B"; + + // Act + var result1 = _generator.GenerateShape(input1); + var result2 = _generator.GenerateShape(input2); + + // Assert + // The lines should be different (different unicode values should generate different angles) + Assert.NotEqual(result1[0].X2, result2[0].X2); + Assert.NotEqual(result1[0].Y2, result2[0].Y2); + } + + [Fact] + public void ShapeLine_PropertiesCanBeSetAndRetrieved() + { + // Arrange + var shapeLine = new ShapeLine(); + + // Act + shapeLine.X1 = 10.5; + shapeLine.Y1 = 20.3; + shapeLine.X2 = 30.7; + shapeLine.Y2 = 40.1; + shapeLine.Color = "rgb(255,0,0)"; + shapeLine.Width = 2; + + // Assert + Assert.Equal(10.5, shapeLine.X1); + Assert.Equal(20.3, shapeLine.Y1); + Assert.Equal(30.7, shapeLine.X2); + Assert.Equal(40.1, shapeLine.Y2); + Assert.Equal("rgb(255,0,0)", shapeLine.Color); + Assert.Equal(2, shapeLine.Width); + } + + [Fact] + public void ShapeLine_DefaultColorIsEmpty() + { + // Arrange & Act + var shapeLine = new ShapeLine(); + + // Assert + Assert.Equal("", shapeLine.Color); + } +} \ No newline at end of file diff --git a/DynamicShapeLogic.Tests/DynamicShapeLogic.Tests.csproj b/DynamicShapeLogic.Tests/DynamicShapeLogic.Tests.csproj new file mode 100644 index 0000000..e442f99 --- /dev/null +++ b/DynamicShapeLogic.Tests/DynamicShapeLogic.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/DynamicShapeLogic.Tests/GlobalUsings.cs b/DynamicShapeLogic.Tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/DynamicShapeLogic.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/DynamicShapeLogic/Class1.cs b/DynamicShapeLogic/DynamicShapeGenerator.cs similarity index 100% rename from DynamicShapeLogic/Class1.cs rename to DynamicShapeLogic/DynamicShapeGenerator.cs diff --git a/TestAgentRepo.sln b/TestAgentRepo.sln index add09f0..c620fa5 100644 --- a/TestAgentRepo.sln +++ b/TestAgentRepo.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAssemblyApp", "WebAssemb EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicShapeLogic", "DynamicShapeLogic\DynamicShapeLogic.csproj", "{49F8A0C6-B08B-4C9C-8C59-734FBB271F05}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamicShapeLogic.Tests", "DynamicShapeLogic.Tests\DynamicShapeLogic.Tests.csproj", "{AF63D29D-A17B-4501-A82D-76644EA4473F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Debug|Any CPU.Build.0 = Debug|Any CPU {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Release|Any CPU.ActiveCfg = Release|Any CPU {49F8A0C6-B08B-4C9C-8C59-734FBB271F05}.Release|Any CPU.Build.0 = Release|Any CPU + {AF63D29D-A17B-4501-A82D-76644EA4473F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF63D29D-A17B-4501-A82D-76644EA4473F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF63D29D-A17B-4501-A82D-76644EA4473F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF63D29D-A17B-4501-A82D-76644EA4473F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 5f9fb8e117cf7a455e40b446c3313cd7bb25ae71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:17:03 +0000 Subject: [PATCH 7/7] Add comprehensive copilot-instructions.md file with development guidelines Co-authored-by: petronivs <49458676+petronivs@users.noreply.github.com> --- copilot-instructions.md | 233 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 copilot-instructions.md diff --git a/copilot-instructions.md b/copilot-instructions.md new file mode 100644 index 0000000..803116d --- /dev/null +++ b/copilot-instructions.md @@ -0,0 +1,233 @@ +# Copilot Instructions for TestAgentRepo + +This document provides comprehensive guidance for AI assistants working on the TestAgentRepo project, a Blazor WebAssembly application with dynamic shape generation capabilities. + +## Project Overview + +TestAgentRepo is a Visual Studio solution containing a Blazor WebAssembly frontend with separated business logic for generating dynamic SVG shapes based on text input. The project demonstrates various shape generation algorithms and serves as a testing ground for AI-assisted development. + +### Key Technologies +- **Frontend**: Blazor WebAssembly (.NET 8.0) +- **Business Logic**: .NET 8.0 Class Library +- **Testing**: xUnit with comprehensive unit tests +- **Deployment**: GitHub Codespaces optimized + +## Architecture Guidelines + +### Project Structure +``` +TestAgentRepo/ +├── WebAssemblyApp/ # Blazor WebAssembly UI project +│ ├── Pages/ # Razor pages for different shape generators +│ ├── Layout/ # Navigation and layout components +│ └── wwwroot/ # Static web assets +├── DynamicShapeLogic/ # Business logic library +│ └── DynamicShapeGenerator.cs # Core shape generation algorithms +├── DynamicShapeLogic.Tests/ # Unit tests for business logic +└── README.md # Project documentation +``` + +### Separation of Concerns +- **UI Layer (WebAssemblyApp)**: Handle user interaction, rendering, and Blazor-specific logic +- **Business Logic (DynamicShapeLogic)**: Contain all shape generation algorithms and mathematical calculations +- **Testing Layer**: Comprehensive unit tests for all business logic + +## Development Guidelines + +### Code Style and Patterns + +#### Naming Conventions +- **Classes**: PascalCase (e.g., `DynamicShapeGenerator`) +- **Methods**: PascalCase (e.g., `GenerateShape`) +- **Properties**: PascalCase (e.g., `X1`, `Y1`, `Color`) +- **Fields**: PascalCase for public, camelCase for private +- **Constants**: UPPER_SNAKE_CASE (e.g., `CENTER_X`, `SVG_WIDTH`) + +#### File Organization +- **Razor Pages**: Name matches functionality (e.g., `DynamicShape2.razor`) +- **Business Logic**: Descriptive class names (e.g., `DynamicShapeGenerator.cs`) +- **Tests**: Match the class being tested with `.Tests` suffix + +#### Shape Generation Patterns +All shape generators should follow this pattern: +1. Accept string input text +2. Return `List` objects +3. Handle null/empty input gracefully +4. Apply bounds checking (SVG_WIDTH: 500, SVG_HEIGHT: 400) +5. Use consistent center point (CENTER_X: 250, CENTER_Y: 200) + +### Data Models + +#### ShapeLine Class +```csharp +public class ShapeLine +{ + public double X1 { get; set; } // Start X coordinate + public double Y1 { get; set; } // Start Y coordinate + public double X2 { get; set; } // End X coordinate + public double Y2 { get; set; } // End Y coordinate + public string Color { get; set; } // Hex color code + public int Width { get; set; } // Line width in pixels +} +``` + +### Algorithm Implementations + +#### DynamicShape vs DynamicShape2 +- **DynamicShape**: Uses incremental 180-degree rotations +- **DynamicShape2**: Uses unicode values directly as angles with line flipping logic + +#### Line Flipping Logic (DynamicShape2) +```csharp +bool pointsAwayFromVerticalCenter = Math.Abs(proposedEndX - centerX) > Math.Abs(currentX - centerX); +bool pointsAwayFromHorizontalCenter = Math.Abs(proposedEndY - centerY) > Math.Abs(currentY - centerY); + +if (pointsAwayFromVerticalCenter && pointsAwayFromHorizontalCenter) { + angle += 180; // Flip the line +} +``` + +## Testing Guidelines + +### Unit Test Requirements +- All business logic methods must have unit tests +- Test edge cases: null input, empty input, single character +- Verify bounds checking and mathematical calculations +- Test color and width generation algorithms +- Ensure line connectivity (end point of one line = start point of next) + +### Test Structure +```csharp +[Fact] +public void GenerateShape_MethodName_ExpectedBehavior() +{ + // Arrange + var generator = new DynamicShapeGenerator(); + + // Act + var result = generator.GenerateShape("test input"); + + // Assert + Assert.NotNull(result); + // Additional assertions +} +``` + +## Building and Testing + +### Build Commands +```bash +# Build entire solution +dotnet build + +# Run WebAssembly app +cd WebAssemblyApp +dotnet run + +# Run tests +dotnet test +``` + +### GitHub Codespaces +- Project is optimized for Codespaces development +- Port 5110 is automatically forwarded +- All dependencies are pre-configured + +## AI Assistant Guidelines + +### When Adding New Features + +1. **Business Logic First**: Implement core algorithms in `DynamicShapeLogic` project +2. **Add Unit Tests**: Create comprehensive tests before UI implementation +3. **UI Integration**: Create Razor page that uses the business logic +4. **Update Navigation**: Add menu entries in layout components +5. **Documentation**: Update README.md with new functionality + +### When Fixing Bugs + +1. **Reproduce with Tests**: Create failing unit test that demonstrates the bug +2. **Fix Business Logic**: Address the issue in the appropriate class +3. **Verify Fix**: Ensure all tests pass, including the new one +4. **UI Validation**: Test the fix in the web interface + +### Code Review Checklist + +- [ ] Business logic separated from UI concerns +- [ ] Unit tests cover all new functionality +- [ ] Null/empty input handling implemented +- [ ] Bounds checking applied to coordinates +- [ ] Consistent naming conventions followed +- [ ] No hardcoded magic numbers (use constants) +- [ ] Color generation follows existing patterns +- [ ] Line connectivity maintained for connected shapes + +### Common Tasks + +#### Adding a New Shape Generator + +1. Create algorithm in `DynamicShapeLogic/DynamicShapeGenerator.cs` +2. Add comprehensive unit tests in `DynamicShapeLogic.Tests` +3. Create new Razor page in `WebAssemblyApp/Pages/` +4. Update navigation menu +5. Update README.md architecture section + +#### Modifying Existing Algorithms + +1. Update business logic method +2. Modify or add unit tests to cover changes +3. Verify existing tests still pass +4. Test changes in web interface + +#### Performance Optimization + +1. Profile shape generation algorithms +2. Optimize mathematical calculations +3. Consider caching for repeated operations +4. Maintain readability while improving performance + +## Mathematical Concepts + +### Coordinate System +- SVG uses top-left origin (0,0) +- Center point is (250, 200) in 500x400 canvas +- Y increases downward + +### Angle Calculations +- Angles in degrees (0-360) +- Conversion to radians: `angle * Math.PI / 180` +- Line endpoints: `(x + length * cos(θ), y + length * sin(θ))` + +### Color Generation +- Uses unicode values for color calculation +- Ensures good contrast and visibility +- Maintains consistency across related lines + +## Project Evolution Notes + +### Recent Changes +- Business logic separated into dedicated project (commit db5f3d4) +- Class1.cs renamed to DynamicShapeGenerator.cs (commit 093389a) +- Comprehensive unit tests added (commit 093389a) +- README updated with Codespace instructions (commit 16da9ce) + +### Future Considerations +- Consider adding more shape generation algorithms +- Explore 3D shape projections +- Add animation capabilities +- Implement shape export functionality + +## Troubleshooting + +### Common Issues +- **Build Failures**: Ensure all project references are correct +- **Test Failures**: Verify mathematical calculations and edge cases +- **UI Not Updating**: Check Blazor component state management +- **Port Issues**: Use different ports if 5110 is unavailable + +### Debug Strategies +1. Use unit tests to isolate business logic issues +2. Add logging to shape generation methods +3. Verify SVG coordinates are within bounds +4. Check browser developer tools for client-side errors + +This document should be updated as the project evolves to maintain accuracy and usefulness for future AI assistants working on this codebase. \ No newline at end of file