Skip to content

Commit 4c3466a

Browse files
test: add MenuSizingHelper unit tests + ShellQuoting edge cases (#142)
Add dedicated MenuSizingHelperTests.cs covering all edge cases for CalculateWindowHeight and ConvertPixelsToViewUnits: - zero/negative pixel inputs - zero DPI fallback to 96 - DPI scaling (96, 120, 144, 192) - minimum height enforcement and clamping - negative/zero work area handling - minimum larger than work area Extend ShellQuotingTests with previously untested metacharacters (], }, )), newline/carriage-return chars, and FormatExecCommand with single and empty argument arrays. Tray tests: 99 → 120 passed Shared tests: 525 → 531 passed (551 total, 20 skipped) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6933239 commit 4c3466a

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

tests/OpenClaw.Shared.Tests/ShellQuotingTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,36 @@ public void QuoteForShell_NullArg_PS_ReturnsEmptySingleQuotes()
176176
{
177177
Assert.Equal("''", ShellQuoting.QuoteForShell(null!, isCmd: false));
178178
}
179+
180+
// ── Additional NeedsQuoting metachar coverage ───────────────────
181+
182+
[Theory]
183+
[InlineData("a]b", ']')]
184+
[InlineData("a}b", '}')]
185+
[InlineData("a)b", ')')]
186+
public void NeedsQuoting_ClosingBracketsAndParen_ReturnsTrue(string arg, char _)
187+
{
188+
Assert.True(ShellQuoting.NeedsQuoting(arg));
189+
}
190+
191+
[Fact]
192+
public void NeedsQuoting_NewlineChars_ReturnTrue()
193+
{
194+
Assert.True(ShellQuoting.NeedsQuoting("line1\nline2"));
195+
Assert.True(ShellQuoting.NeedsQuoting("line1\rline2"));
196+
}
197+
198+
// ── FormatExecCommand edge cases ────────────────────────────────
199+
200+
[Fact]
201+
public void FormatExecCommand_SingleArg_ReturnsArgUnchanged()
202+
{
203+
Assert.Equal("echo", ShellQuoting.FormatExecCommand(new[] { "echo" }));
204+
}
205+
206+
[Fact]
207+
public void FormatExecCommand_EmptyArray_ReturnsEmptyString()
208+
{
209+
Assert.Equal("", ShellQuoting.FormatExecCommand(Array.Empty<string>()));
210+
}
179211
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using OpenClaw.Shared;
2+
3+
namespace OpenClaw.Tray.Tests;
4+
5+
/// <summary>
6+
/// Unit tests for MenuSizingHelper — CalculateWindowHeight and ConvertPixelsToViewUnits.
7+
/// </summary>
8+
public class MenuSizingHelperTests
9+
{
10+
// ── ConvertPixelsToViewUnits ────────────────────────────────────
11+
12+
[Fact]
13+
public void ConvertPixels_ZeroPixels_ReturnsZero()
14+
{
15+
Assert.Equal(0, MenuSizingHelper.ConvertPixelsToViewUnits(0, 96));
16+
}
17+
18+
[Fact]
19+
public void ConvertPixels_NegativePixels_ReturnsZero()
20+
{
21+
Assert.Equal(0, MenuSizingHelper.ConvertPixelsToViewUnits(-100, 96));
22+
}
23+
24+
[Fact]
25+
public void ConvertPixels_StandardDpi96_ReturnsSameValue()
26+
{
27+
// At 100% scale (96 DPI) view units == pixels
28+
Assert.Equal(200, MenuSizingHelper.ConvertPixelsToViewUnits(200, 96));
29+
Assert.Equal(1080, MenuSizingHelper.ConvertPixelsToViewUnits(1080, 96));
30+
}
31+
32+
[Fact]
33+
public void ConvertPixels_ZeroDpi_FallsBackTo96()
34+
{
35+
// DPI 0 is treated as 96
36+
Assert.Equal(200, MenuSizingHelper.ConvertPixelsToViewUnits(200, 0));
37+
}
38+
39+
[Fact]
40+
public void ConvertPixels_Dpi192_HalvesValue()
41+
{
42+
// 200% scale: 1200 physical px → 600 view units
43+
Assert.Equal(600, MenuSizingHelper.ConvertPixelsToViewUnits(1200, 192));
44+
}
45+
46+
[Fact]
47+
public void ConvertPixels_Dpi144_FloorsDivision()
48+
{
49+
// 150% scale: 100 px → floor(100 * 96 / 144) = floor(66.6) = 66
50+
Assert.Equal(66, MenuSizingHelper.ConvertPixelsToViewUnits(100, 144));
51+
}
52+
53+
[Fact]
54+
public void ConvertPixels_HighDpi_ReturnsAtLeastOne()
55+
{
56+
// Even with extremely high DPI, result must be at least 1
57+
Assert.Equal(1, MenuSizingHelper.ConvertPixelsToViewUnits(1, 960));
58+
}
59+
60+
[Fact]
61+
public void ConvertPixels_Dpi120_CorrectScaling()
62+
{
63+
// 125% scale: 1000 px → floor(1000 * 96 / 120) = floor(800) = 800
64+
Assert.Equal(800, MenuSizingHelper.ConvertPixelsToViewUnits(1000, 120));
65+
}
66+
67+
// ── CalculateWindowHeight ───────────────────────────────────────
68+
69+
[Fact]
70+
public void CalcHeight_ContentSmallerThanWorkArea_ReturnsContent()
71+
{
72+
// Normal case: content fits, no minimum constraint
73+
Assert.Equal(300, MenuSizingHelper.CalculateWindowHeight(300, 1000, minimumHeight: 100));
74+
}
75+
76+
[Fact]
77+
public void CalcHeight_ContentLargerThanWorkArea_ClampsToWorkArea()
78+
{
79+
// Content too tall for screen → clamp to work area
80+
Assert.Equal(800, MenuSizingHelper.CalculateWindowHeight(1200, 800, minimumHeight: 100));
81+
}
82+
83+
[Fact]
84+
public void CalcHeight_ContentSmallerThanMinimum_ReturnsMinimum()
85+
{
86+
// Small content → at least minimumHeight
87+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(50, 1000, minimumHeight: 100));
88+
}
89+
90+
[Fact]
91+
public void CalcHeight_DefaultMinimumIs100()
92+
{
93+
// Verify default parameter value
94+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(0, 1000));
95+
}
96+
97+
[Fact]
98+
public void CalcHeight_NegativeContent_TreatedAsZero()
99+
{
100+
// Negative content → clamped to 0, result is minimumHeight
101+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(-50, 1000, minimumHeight: 100));
102+
}
103+
104+
[Fact]
105+
public void CalcHeight_MinimumLessThanOne_SetToOne()
106+
{
107+
// minimumHeight < 1 → forced to 1
108+
Assert.Equal(1, MenuSizingHelper.CalculateWindowHeight(0, 1000, minimumHeight: 0));
109+
Assert.Equal(1, MenuSizingHelper.CalculateWindowHeight(0, 1000, minimumHeight: -50));
110+
}
111+
112+
[Fact]
113+
public void CalcHeight_ZeroWorkArea_ReturnsMaxContentMinimum()
114+
{
115+
// workAreaHeight <= 0: can't constrain, return max(content, minimum)
116+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(50, 0, minimumHeight: 100));
117+
Assert.Equal(200, MenuSizingHelper.CalculateWindowHeight(200, 0, minimumHeight: 100));
118+
}
119+
120+
[Fact]
121+
public void CalcHeight_NegativeWorkArea_ReturnsMaxContentMinimum()
122+
{
123+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(50, -200, minimumHeight: 100));
124+
}
125+
126+
[Fact]
127+
public void CalcHeight_MinimumLargerThanWorkArea_ClampsMinimumToWorkArea()
128+
{
129+
// minimumHeight (200) > workAreaHeight (100) → result capped at workAreaHeight
130+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(50, 100, minimumHeight: 200));
131+
}
132+
133+
[Fact]
134+
public void CalcHeight_ContentEqualsWorkArea_ReturnsWorkArea()
135+
{
136+
Assert.Equal(800, MenuSizingHelper.CalculateWindowHeight(800, 800, minimumHeight: 100));
137+
}
138+
139+
[Fact]
140+
public void CalcHeight_ContentEqualsMinimum_ReturnsMinimum()
141+
{
142+
Assert.Equal(100, MenuSizingHelper.CalculateWindowHeight(100, 1000, minimumHeight: 100));
143+
}
144+
145+
[Fact]
146+
public void CalcHeight_TypicalTrayMenuScenario_FitsInWorkArea()
147+
{
148+
// Typical: 400px content, 1040px work area (1080 - 40px taskbar), 100px min
149+
var height = MenuSizingHelper.CalculateWindowHeight(400, 1040, minimumHeight: 100);
150+
Assert.Equal(400, height);
151+
Assert.True(height <= 1040);
152+
}
153+
154+
[Fact]
155+
public void CalcHeight_OversizedMenu_ClampsToWorkArea()
156+
{
157+
// Lots of sessions → menu taller than screen
158+
var height = MenuSizingHelper.CalculateWindowHeight(2000, 1040, minimumHeight: 100);
159+
Assert.Equal(1040, height);
160+
}
161+
}

0 commit comments

Comments
 (0)