|
| 1 | +// ----------------------------------------------------------------------- |
| 2 | +// ConsoleEx - A simple console window system for .NET Core |
| 3 | +// |
| 4 | +// Author: Nikolaos Protopapas |
| 5 | +// Email: nikolaos.protopapas@gmail.com |
| 6 | +// License: MIT |
| 7 | +// ----------------------------------------------------------------------- |
| 8 | + |
| 9 | +using SharpConsoleUI.Drawing; |
| 10 | +using SharpConsoleUI.Layout; |
| 11 | +using Spectre.Console; |
| 12 | +using Xunit; |
| 13 | + |
| 14 | +namespace SharpConsoleUI.Tests.Rendering.Unit.TopLayer; |
| 15 | + |
| 16 | +/// <summary> |
| 17 | +/// Tests for CharacterBuffer - the top layer of the rendering pipeline. |
| 18 | +/// Validates cell operations, dirty tracking, and buffer manipulation. |
| 19 | +/// </summary> |
| 20 | +public class CharacterBufferTests |
| 21 | +{ |
| 22 | + [Fact] |
| 23 | + public void CharacterBuffer_Create_HasCorrectDimensions() |
| 24 | + { |
| 25 | + // Arrange & Act |
| 26 | + var buffer = new CharacterBuffer(80, 25); |
| 27 | + |
| 28 | + // Assert |
| 29 | + Assert.Equal(80, buffer.Width); |
| 30 | + Assert.Equal(25, buffer.Height); |
| 31 | + } |
| 32 | + |
| 33 | + [Fact] |
| 34 | + public void CharacterBuffer_SetCell_UpdatesCell() |
| 35 | + { |
| 36 | + // Arrange |
| 37 | + var buffer = new CharacterBuffer(10, 10); |
| 38 | + |
| 39 | + // Act |
| 40 | + buffer.SetCell(5, 5, 'X', Color.Red, Color.Blue); |
| 41 | + |
| 42 | + // Assert |
| 43 | + var cell = buffer.GetCell(5, 5); |
| 44 | + Assert.Equal('X', cell.Character); |
| 45 | + Assert.Equal(Color.Red, cell.Foreground); |
| 46 | + Assert.Equal(Color.Blue, cell.Background); |
| 47 | + } |
| 48 | + |
| 49 | + [Fact] |
| 50 | + public void CharacterBuffer_WriteString_WritesAllCharacters() |
| 51 | + { |
| 52 | + // Arrange |
| 53 | + var buffer = new CharacterBuffer(20, 10); |
| 54 | + var text = "Hello"; |
| 55 | + |
| 56 | + // Act |
| 57 | + buffer.WriteString(5, 3, text, Color.White, Color.Black); |
| 58 | + |
| 59 | + // Assert |
| 60 | + Assert.Equal('H', buffer.GetCell(5, 3).Character); |
| 61 | + Assert.Equal('e', buffer.GetCell(6, 3).Character); |
| 62 | + Assert.Equal('l', buffer.GetCell(7, 3).Character); |
| 63 | + Assert.Equal('l', buffer.GetCell(8, 3).Character); |
| 64 | + Assert.Equal('o', buffer.GetCell(9, 3).Character); |
| 65 | + } |
| 66 | + |
| 67 | + [Fact] |
| 68 | + public void CharacterBuffer_WriteStringClipped_RespectsClipRect() |
| 69 | + { |
| 70 | + // Arrange |
| 71 | + var buffer = new CharacterBuffer(20, 10); |
| 72 | + var text = "Hello World"; |
| 73 | + var clipRect = new LayoutRect(5, 3, 5, 1); // Only 5 chars wide |
| 74 | + |
| 75 | + // Act |
| 76 | + buffer.WriteStringClipped(5, 3, text, Color.White, Color.Black, clipRect); |
| 77 | + |
| 78 | + // Assert - Only "Hello" should be written (5 chars) |
| 79 | + Assert.Equal('H', buffer.GetCell(5, 3).Character); |
| 80 | + Assert.Equal('o', buffer.GetCell(9, 3).Character); |
| 81 | + // "World" should not be written |
| 82 | + Assert.NotEqual('W', buffer.GetCell(10, 3).Character); |
| 83 | + } |
| 84 | + |
| 85 | + [Fact] |
| 86 | + public void CharacterBuffer_FillRect_FillsEntireRegion() |
| 87 | + { |
| 88 | + // Arrange |
| 89 | + var buffer = new CharacterBuffer(20, 10); |
| 90 | + var rect = new LayoutRect(5, 3, 10, 4); |
| 91 | + |
| 92 | + // Act |
| 93 | + buffer.FillRect(rect, '#', Color.Yellow, Color.Green); |
| 94 | + |
| 95 | + // Assert - Check corners and middle |
| 96 | + Assert.Equal('#', buffer.GetCell(5, 3).Character); // Top-left |
| 97 | + Assert.Equal('#', buffer.GetCell(14, 6).Character); // Bottom-right |
| 98 | + Assert.Equal('#', buffer.GetCell(10, 5).Character); // Middle |
| 99 | + Assert.Equal(Color.Yellow, buffer.GetCell(10, 5).Foreground); |
| 100 | + Assert.Equal(Color.Green, buffer.GetCell(10, 5).Background); |
| 101 | + } |
| 102 | + |
| 103 | + [Fact] |
| 104 | + public void CharacterBuffer_Clear_ClearsAllCells() |
| 105 | + { |
| 106 | + // Arrange |
| 107 | + var buffer = new CharacterBuffer(10, 10); |
| 108 | + buffer.SetCell(5, 5, 'X', Color.Red, Color.Blue); |
| 109 | + |
| 110 | + // Act |
| 111 | + buffer.Clear(Color.Black); |
| 112 | + |
| 113 | + // Assert |
| 114 | + var cell = buffer.GetCell(5, 5); |
| 115 | + Assert.Equal(' ', cell.Character); |
| 116 | + Assert.Equal(Color.Black, cell.Background); |
| 117 | + } |
| 118 | + |
| 119 | + [Fact] |
| 120 | + public void CharacterBuffer_DrawHorizontalLine_DrawsLine() |
| 121 | + { |
| 122 | + // Arrange |
| 123 | + var buffer = new CharacterBuffer(20, 10); |
| 124 | + |
| 125 | + // Act |
| 126 | + buffer.DrawHorizontalLine(5, 5, 10, '─', Color.White, Color.Black); |
| 127 | + |
| 128 | + // Assert |
| 129 | + for (int x = 5; x < 15; x++) |
| 130 | + { |
| 131 | + Assert.Equal('─', buffer.GetCell(x, 5).Character); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + [Fact] |
| 136 | + public void CharacterBuffer_DrawVerticalLine_DrawsLine() |
| 137 | + { |
| 138 | + // Arrange |
| 139 | + var buffer = new CharacterBuffer(20, 10); |
| 140 | + |
| 141 | + // Act |
| 142 | + buffer.DrawVerticalLine(5, 2, 6, '│', Color.White, Color.Black); |
| 143 | + |
| 144 | + // Assert |
| 145 | + for (int y = 2; y < 8; y++) |
| 146 | + { |
| 147 | + Assert.Equal('│', buffer.GetCell(5, y).Character); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + [Fact] |
| 152 | + public void CharacterBuffer_DrawBox_DrawsCompleteBox() |
| 153 | + { |
| 154 | + // Arrange |
| 155 | + var buffer = new CharacterBuffer(20, 10); |
| 156 | + var rect = new LayoutRect(5, 3, 10, 5); |
| 157 | + var boxChars = BoxChars.Single; |
| 158 | + |
| 159 | + // Act |
| 160 | + buffer.DrawBox(rect, boxChars, Color.White, Color.Black); |
| 161 | + |
| 162 | + // Assert - Check corners |
| 163 | + Assert.Equal(boxChars.TopLeft, buffer.GetCell(5, 3).Character); |
| 164 | + Assert.Equal(boxChars.TopRight, buffer.GetCell(14, 3).Character); |
| 165 | + Assert.Equal(boxChars.BottomLeft, buffer.GetCell(5, 7).Character); |
| 166 | + Assert.Equal(boxChars.BottomRight, buffer.GetCell(14, 7).Character); |
| 167 | + |
| 168 | + // Check edges |
| 169 | + Assert.Equal(boxChars.Horizontal, buffer.GetCell(10, 3).Character); // Top |
| 170 | + Assert.Equal(boxChars.Horizontal, buffer.GetCell(10, 7).Character); // Bottom |
| 171 | + Assert.Equal(boxChars.Vertical, buffer.GetCell(5, 5).Character); // Left |
| 172 | + Assert.Equal(boxChars.Vertical, buffer.GetCell(14, 5).Character); // Right |
| 173 | + } |
| 174 | + |
| 175 | + [Fact] |
| 176 | + public void CharacterBuffer_GetDirtyCells_ReturnsModifiedCells() |
| 177 | + { |
| 178 | + // Arrange |
| 179 | + var buffer = new CharacterBuffer(10, 10); |
| 180 | + // Clear initial dirty state by rendering once |
| 181 | + buffer.ToLines(Color.White, Color.Black); |
| 182 | + |
| 183 | + // Act |
| 184 | + buffer.SetCell(3, 4, 'A', Color.Red, Color.Black); |
| 185 | + buffer.SetCell(7, 2, 'B', Color.Blue, Color.White); |
| 186 | + |
| 187 | + var dirtyCells = buffer.GetDirtyCells().ToList(); |
| 188 | + |
| 189 | + // Assert - At minimum, the two cells we modified should be in the dirty list |
| 190 | + Assert.True(dirtyCells.Count >= 2); |
| 191 | + Assert.Contains(dirtyCells, c => c.X == 3 && c.Y == 4); |
| 192 | + Assert.Contains(dirtyCells, c => c.X == 7 && c.Y == 2); |
| 193 | + } |
| 194 | + |
| 195 | + [Fact] |
| 196 | + public void CharacterBuffer_GetChanges_ReturnsAllChanges() |
| 197 | + { |
| 198 | + // Arrange |
| 199 | + var buffer = new CharacterBuffer(10, 10); |
| 200 | + // Clear initial dirty state by rendering once |
| 201 | + buffer.ToLines(Color.White, Color.Black); |
| 202 | + |
| 203 | + // Act |
| 204 | + buffer.SetCell(3, 4, 'A', Color.Red, Color.Black); |
| 205 | + buffer.SetCell(7, 2, 'B', Color.Blue, Color.White); |
| 206 | + |
| 207 | + var changes = buffer.GetChanges().ToList(); |
| 208 | + |
| 209 | + // Assert - Should include our modifications |
| 210 | + Assert.True(changes.Count >= 2); |
| 211 | + var change1 = changes.First(c => c.X == 3 && c.Y == 4); |
| 212 | + Assert.Equal('A', change1.Cell.Character); |
| 213 | + Assert.Equal(Color.Red, change1.Cell.Foreground); |
| 214 | + } |
| 215 | + |
| 216 | + [Fact] |
| 217 | + public void CharacterBuffer_Resize_PreservesContent() |
| 218 | + { |
| 219 | + // Arrange |
| 220 | + var buffer = new CharacterBuffer(10, 10); |
| 221 | + buffer.SetCell(5, 5, 'X', Color.Red, Color.Blue); |
| 222 | + |
| 223 | + // Act |
| 224 | + buffer.Resize(20, 20); |
| 225 | + |
| 226 | + // Assert |
| 227 | + Assert.Equal(20, buffer.Width); |
| 228 | + Assert.Equal(20, buffer.Height); |
| 229 | + // Content should still be there |
| 230 | + var cell = buffer.GetCell(5, 5); |
| 231 | + Assert.Equal('X', cell.Character); |
| 232 | + Assert.Equal(Color.Red, cell.Foreground); |
| 233 | + } |
| 234 | + |
| 235 | + [Fact] |
| 236 | + public void CharacterBuffer_Resize_TruncatesWhenShrinking() |
| 237 | + { |
| 238 | + // Arrange |
| 239 | + var buffer = new CharacterBuffer(20, 20); |
| 240 | + buffer.SetCell(15, 15, 'X', Color.Red, Color.Blue); |
| 241 | + |
| 242 | + // Act - Shrink to 10x10 |
| 243 | + buffer.Resize(10, 10); |
| 244 | + |
| 245 | + // Assert |
| 246 | + Assert.Equal(10, buffer.Width); |
| 247 | + Assert.Equal(10, buffer.Height); |
| 248 | + // Cell at (15,15) should no longer be accessible |
| 249 | + } |
| 250 | + |
| 251 | + [Fact] |
| 252 | + public void CharacterBuffer_ToLines_GeneratesAnsiOutput() |
| 253 | + { |
| 254 | + // Arrange |
| 255 | + var buffer = new CharacterBuffer(10, 3); |
| 256 | + buffer.WriteString(0, 0, "Red", Color.Red, Color.Black); |
| 257 | + buffer.WriteString(0, 1, "Blue", Color.Blue, Color.Black); |
| 258 | + |
| 259 | + // Act |
| 260 | + var lines = buffer.ToLines(Color.White, Color.Black); |
| 261 | + |
| 262 | + // Assert |
| 263 | + Assert.Equal(3, lines.Count); |
| 264 | + Assert.Contains("Red", lines[0]); |
| 265 | + Assert.Contains("Blue", lines[1]); |
| 266 | + // Lines should contain ANSI escape sequences |
| 267 | + Assert.Contains("\x1b[", lines[0]); // ANSI escape start |
| 268 | + } |
| 269 | + |
| 270 | + [Fact] |
| 271 | + public void CharacterBuffer_ClearRect_ClearsOnlySpecifiedRegion() |
| 272 | + { |
| 273 | + // Arrange |
| 274 | + var buffer = new CharacterBuffer(20, 10); |
| 275 | + buffer.FillRect(new LayoutRect(0, 0, 20, 10), 'X', Color.White, Color.Black); |
| 276 | + |
| 277 | + // Act - Clear only a small region |
| 278 | + buffer.ClearRect(new LayoutRect(5, 3, 5, 3), Color.Red); |
| 279 | + |
| 280 | + // Assert |
| 281 | + // Inside cleared region |
| 282 | + Assert.Equal(' ', buffer.GetCell(7, 5).Character); |
| 283 | + Assert.Equal(Color.Red, buffer.GetCell(7, 5).Background); |
| 284 | + |
| 285 | + // Outside cleared region (should still have X) |
| 286 | + Assert.Equal('X', buffer.GetCell(2, 2).Character); |
| 287 | + Assert.Equal('X', buffer.GetCell(15, 7).Character); |
| 288 | + } |
| 289 | +} |
0 commit comments