Skip to content

Commit 2c179f4

Browse files
committed
Add Phase 4 performance tests (partial)
StaticContentTests.cs (8 tests): - Critical quality gate: static content must produce ZERO output - Tests static content across multiple frames, windows, scenarios - 1 test passing (MultipleFrames), 7 tests need threshold adjustments IncrementalUpdateTests.cs (9 tests): - Tests incremental updates produce minimal output - Single char changes, status updates, cursor blinks, etc. - Tests need threshold adjustments for realistic byte counts Update TEST_IMPLEMENTATION_PLAN.md: - Mark Phase 3 as COMPLETE (196 tests passing!) - Update progress: 27 files created, 28% complete - Document Smart mode implementation as bonus achievement - Set Phase 4 as next target Current: 211 tests total (201 passing, 10 need adjustment)
1 parent 44fdab8 commit 2c179f4

3 files changed

Lines changed: 646 additions & 38 deletions

File tree

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
using SharpConsoleUI;
2+
using SharpConsoleUI.Controls;
3+
using SharpConsoleUI.Tests.Infrastructure;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
namespace SharpConsoleUI.Tests.Rendering.Performance;
8+
9+
/// <summary>
10+
/// Tests that incremental updates produce minimal output.
11+
/// Small changes should only output the changed regions, not entire windows.
12+
/// </summary>
13+
public class IncrementalUpdateTests
14+
{
15+
private readonly ITestOutputHelper _output;
16+
17+
public IncrementalUpdateTests(ITestOutputHelper output)
18+
{
19+
_output = output;
20+
}
21+
22+
[Fact]
23+
public void SingleCharacterChange_MinimalOutput()
24+
{
25+
// Changing 1 character should output < 50 bytes
26+
27+
// Arrange
28+
var system = TestWindowSystemBuilder.CreateTestSystem();
29+
var control = new MarkupControl(new List<string> { "AAAA" });
30+
var window = new Window(system) { Left = 10, Top = 5, Width = 40, Height = 10 };
31+
window.AddControl(control);
32+
system.WindowStateService.AddWindow(window);
33+
34+
// Frame 1: Initial render
35+
system.Render.UpdateDisplay();
36+
37+
// Act - Frame 2: Change 1 character
38+
control.SetContent(new List<string> { "ABAA" });
39+
window.Invalidate(true);
40+
system.Render.UpdateDisplay();
41+
42+
var metrics = system.RenderingDiagnostics?.LastMetrics;
43+
Assert.NotNull(metrics);
44+
45+
_output.WriteLine($"1 char change: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
46+
47+
// Assert - Minimal output (Smart mode should use CELL mode)
48+
Assert.Equal(1, metrics.CharactersChanged);
49+
Assert.True(metrics.BytesWritten < 50,
50+
$"Expected <50 bytes for 1 char change, got {metrics.BytesWritten}");
51+
}
52+
53+
[Fact]
54+
public void SmallTextChange_MinimalOutput()
55+
{
56+
// Changing a small word should output < 100 bytes
57+
58+
// Arrange
59+
var system = TestWindowSystemBuilder.CreateTestSystem();
60+
var control = new MarkupControl(new List<string> { "Hello World" });
61+
var window = new Window(system) { Left = 10, Top = 5, Width = 50, Height = 15 };
62+
window.AddControl(control);
63+
system.WindowStateService.AddWindow(window);
64+
65+
// Frame 1: Initial render
66+
system.Render.UpdateDisplay();
67+
68+
// Act - Frame 2: Change "World" to "There"
69+
control.SetContent(new List<string> { "Hello There" });
70+
window.Invalidate(true);
71+
system.Render.UpdateDisplay();
72+
73+
var metrics = system.RenderingDiagnostics?.LastMetrics;
74+
Assert.NotNull(metrics);
75+
76+
_output.WriteLine($"Small text change: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
77+
78+
// Assert - Minimal output
79+
Assert.True(metrics.CharactersChanged >= 5); // "There" = 5 chars
80+
Assert.True(metrics.BytesWritten < 100,
81+
$"Expected <100 bytes for small word change, got {metrics.BytesWritten}");
82+
}
83+
84+
[Fact]
85+
public void StatusBarUpdate_MinimalOutput()
86+
{
87+
// Simulates typical status bar update (time, status text)
88+
// Should be very efficient (<50 bytes for ~8 char change)
89+
90+
// Arrange
91+
var system = TestWindowSystemBuilder.CreateTestSystem();
92+
var control = new MarkupControl(new List<string> { "Status: 12:34:56" });
93+
var window = new Window(system) { Left = 0, Top = 0, Width = 80, Height = 3 };
94+
window.AddControl(control);
95+
system.WindowStateService.AddWindow(window);
96+
97+
// Frame 1: Initial render
98+
system.Render.UpdateDisplay();
99+
100+
// Act - Frame 2: Update time (8 chars changed)
101+
control.SetContent(new List<string> { "Status: 12:34:57" });
102+
window.Invalidate(true);
103+
system.Render.UpdateDisplay();
104+
105+
var metrics = system.RenderingDiagnostics?.LastMetrics;
106+
Assert.NotNull(metrics);
107+
108+
_output.WriteLine($"Status update: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
109+
110+
// Assert - Very efficient for status bar updates
111+
Assert.True(metrics.CharactersChanged <= 8);
112+
Assert.True(metrics.BytesWritten < 50,
113+
$"Expected <50 bytes for status update, got {metrics.BytesWritten}");
114+
}
115+
116+
[Fact]
117+
public void CursorBlink_MinimalOutput()
118+
{
119+
// Cursor blink (2 cells: old position + new position) should be very efficient
120+
121+
// Arrange
122+
var system = TestWindowSystemBuilder.CreateTestSystem();
123+
var control = new MarkupControl(new List<string> { "Text_here" });
124+
var window = new Window(system) { Left = 10, Top = 5, Width = 40, Height = 10 };
125+
window.AddControl(control);
126+
system.WindowStateService.AddWindow(window);
127+
128+
// Frame 1: Initial render with cursor at position 4
129+
system.Render.UpdateDisplay();
130+
131+
// Act - Frame 2: Move cursor (change 2 cells)
132+
control.SetContent(new List<string> { "Text here" }); // Remove underscore, simulates cursor move
133+
window.Invalidate(true);
134+
system.Render.UpdateDisplay();
135+
136+
var metrics = system.RenderingDiagnostics?.LastMetrics;
137+
Assert.NotNull(metrics);
138+
139+
_output.WriteLine($"Cursor blink: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
140+
141+
// Assert - Very minimal for cursor updates
142+
Assert.True(metrics.CharactersChanged <= 2);
143+
Assert.True(metrics.BytesWritten < 50,
144+
$"Expected <50 bytes for cursor update, got {metrics.BytesWritten}");
145+
}
146+
147+
[Fact]
148+
public void ListSelectionChange_ReasonableOutput()
149+
{
150+
// Changing list selection (2 lines: old + new) should be efficient
151+
152+
// Arrange
153+
var system = TestWindowSystemBuilder.CreateTestSystem();
154+
var control = new MarkupControl(new List<string>
155+
{
156+
"[reverse]Item 1[/]", // Selected
157+
"Item 2",
158+
"Item 3"
159+
});
160+
var window = new Window(system) { Left = 10, Top = 5, Width = 40, Height = 15 };
161+
window.AddControl(control);
162+
system.WindowStateService.AddWindow(window);
163+
164+
// Frame 1: Initial render
165+
system.Render.UpdateDisplay();
166+
167+
// Act - Frame 2: Change selection to Item 2
168+
control.SetContent(new List<string>
169+
{
170+
"Item 1", // Deselected
171+
"[reverse]Item 2[/]", // Selected
172+
"Item 3"
173+
});
174+
window.Invalidate(true);
175+
system.Render.UpdateDisplay();
176+
177+
var metrics = system.RenderingDiagnostics?.LastMetrics;
178+
Assert.NotNull(metrics);
179+
180+
_output.WriteLine($"List selection change: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
181+
182+
// Assert - Reasonable output for 2 line changes
183+
Assert.True(metrics.CharactersChanged >= 12); // "Item 1" + "Item 2" = 12 chars minimum
184+
Assert.True(metrics.BytesWritten < 200,
185+
$"Expected <200 bytes for selection change, got {metrics.BytesWritten}");
186+
}
187+
188+
[Fact]
189+
public void MultipleSmallChanges_CumulativeMinimal()
190+
{
191+
// Multiple small changes across different windows should be efficient
192+
193+
// Arrange
194+
var system = TestWindowSystemBuilder.CreateTestSystem();
195+
196+
var window1 = new Window(system) { Left = 10, Top = 5, Width = 30, Height = 8 };
197+
var control1 = new MarkupControl(new List<string> { "Window 1: A" });
198+
window1.AddControl(control1);
199+
200+
var window2 = new Window(system) { Left = 50, Top = 10, Width = 30, Height = 8 };
201+
var control2 = new MarkupControl(new List<string> { "Window 2: B" });
202+
window2.AddControl(control2);
203+
204+
var window3 = new Window(system) { Left = 90, Top = 15, Width = 30, Height = 8 };
205+
var control3 = new MarkupControl(new List<string> { "Window 3: C" });
206+
window3.AddControl(control3);
207+
208+
system.WindowStateService.AddWindow(window1);
209+
system.WindowStateService.AddWindow(window2);
210+
system.WindowStateService.AddWindow(window3);
211+
212+
// Frame 1: Initial render
213+
system.Render.UpdateDisplay();
214+
215+
// Act - Frame 2: Change 1 character in each window (3 total)
216+
control1.SetContent(new List<string> { "Window 1: X" });
217+
window1.Invalidate(true);
218+
219+
control2.SetContent(new List<string> { "Window 2: Y" });
220+
window2.Invalidate(true);
221+
222+
control3.SetContent(new List<string> { "Window 3: Z" });
223+
window3.Invalidate(true);
224+
225+
system.Render.UpdateDisplay();
226+
227+
var metrics = system.RenderingDiagnostics?.LastMetrics;
228+
Assert.NotNull(metrics);
229+
230+
_output.WriteLine($"3 small changes: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
231+
232+
// Assert - Efficient cumulative output
233+
Assert.Equal(3, metrics.CharactersChanged);
234+
Assert.True(metrics.BytesWritten < 150,
235+
$"Expected <150 bytes for 3 small changes, got {metrics.BytesWritten}");
236+
}
237+
238+
[Fact]
239+
public void ProgressBarUpdate_ReasonableOutput()
240+
{
241+
// Progress bar update (one line changing ~10 chars) should be efficient
242+
243+
// Arrange
244+
var system = TestWindowSystemBuilder.CreateTestSystem();
245+
var control = new MarkupControl(new List<string> { "[green]████████[/] [50%]" });
246+
var window = new Window(system) { Left = 10, Top = 5, Width = 50, Height = 10 };
247+
window.AddControl(control);
248+
system.WindowStateService.AddWindow(window);
249+
250+
// Frame 1: Initial render
251+
system.Render.UpdateDisplay();
252+
253+
// Act - Frame 2: Update progress
254+
control.SetContent(new List<string> { "[green]██████████[/] [60%]" });
255+
window.Invalidate(true);
256+
system.Render.UpdateDisplay();
257+
258+
var metrics = system.RenderingDiagnostics?.LastMetrics;
259+
Assert.NotNull(metrics);
260+
261+
_output.WriteLine($"Progress update: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
262+
263+
// Assert - Reasonable output for progress bar
264+
Assert.True(metrics.CharactersChanged >= 10); // Changed ~10 characters
265+
Assert.True(metrics.BytesWritten < 200,
266+
$"Expected <200 bytes for progress update, got {metrics.BytesWritten}");
267+
}
268+
269+
[Fact]
270+
public void PartialLineUpdate_OnlyChangedRegion()
271+
{
272+
// Update in middle of line should only output that region
273+
274+
// Arrange
275+
var system = TestWindowSystemBuilder.CreateTestSystem();
276+
var longLine = "The quick brown fox jumps over the lazy dog";
277+
var control = new MarkupControl(new List<string> { longLine });
278+
var window = new Window(system) { Left = 10, Top = 5, Width = 60, Height = 10 };
279+
window.AddControl(control);
280+
system.WindowStateService.AddWindow(window);
281+
282+
// Frame 1: Initial render
283+
system.Render.UpdateDisplay();
284+
285+
// Act - Frame 2: Change "brown" to "BLACK" (middle of line)
286+
var updatedLine = "The quick BLACK fox jumps over the lazy dog";
287+
control.SetContent(new List<string> { updatedLine });
288+
window.Invalidate(true);
289+
system.Render.UpdateDisplay();
290+
291+
var metrics = system.RenderingDiagnostics?.LastMetrics;
292+
Assert.NotNull(metrics);
293+
294+
_output.WriteLine($"Partial line update: {metrics.BytesWritten} bytes, {metrics.CharactersChanged} chars");
295+
296+
// Assert - Only changed region output (Smart mode should use CELL mode)
297+
Assert.Equal(5, metrics.CharactersChanged); // "BLACK" = 5 chars
298+
Assert.True(metrics.BytesWritten < 100,
299+
$"Expected <100 bytes for partial line, got {metrics.BytesWritten}");
300+
}
301+
}

0 commit comments

Comments
 (0)