Skip to content

Commit 3787f37

Browse files
perditavojoCopilot
andcommitted
test: add unit tests for DialogLayoutHelper, VibrationPatterns, InputBoxLayoutManager
- DialogLayoutHelperTests: 9 tests for TryBeginDpiLayout DPI debounce and GetMaxFitSize boundary math - VibrationPatternsTests: 12 tests verifying static vibration profile values and GlobalIntensityMultiplier - InputBoxLayoutManagerTests: 4 tests for UpdateMinimumSize DPI debounce callback logic Increases test count from 124 to 150 (+21%) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f8247b2 commit 3787f37

3 files changed

Lines changed: 369 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using InputBox.Core.Utilities;
2+
using Xunit;
3+
4+
namespace InputBox.Tests;
5+
6+
/// <summary>
7+
/// DialogLayoutHelper 的 DPI 防抖與工作區計算邏輯測試
8+
/// </summary>
9+
public class DialogLayoutHelperTests
10+
{
11+
// ── TryBeginDpiLayout ──────────────────────────────────────────────────
12+
13+
/// <summary>
14+
/// DPI 沒有變化時(差值 &lt; 0.01),不應觸發重新計算,回傳 false 且快取不變。
15+
/// </summary>
16+
[Fact]
17+
public void TryBeginDpiLayout_SameDpi_ReturnsFalse()
18+
{
19+
float lastDpi = 96f;
20+
bool result = DialogLayoutHelper.TryBeginDpiLayout(96f, ref lastDpi);
21+
22+
Assert.False(result);
23+
Assert.Equal(96f, lastDpi);
24+
}
25+
26+
/// <summary>
27+
/// DPI 在 0.01 以內的微小差值不應視為變化,回傳 false。
28+
/// </summary>
29+
[Fact]
30+
public void TryBeginDpiLayout_TinyDiff_ReturnsFalse()
31+
{
32+
float lastDpi = 96f;
33+
bool result = DialogLayoutHelper.TryBeginDpiLayout(96.005f, ref lastDpi);
34+
35+
Assert.False(result);
36+
}
37+
38+
/// <summary>
39+
/// DPI 變化超過 0.01 時,應觸發重新計算,回傳 true 並更新快取值。
40+
/// </summary>
41+
[Fact]
42+
public void TryBeginDpiLayout_ChangedDpi_ReturnsTrueAndUpdatesCache()
43+
{
44+
float lastDpi = 96f;
45+
bool result = DialogLayoutHelper.TryBeginDpiLayout(120f, ref lastDpi);
46+
47+
Assert.True(result);
48+
Assert.Equal(120f, lastDpi);
49+
}
50+
51+
/// <summary>
52+
/// forceRecalculate = true 時,即使 DPI 相同也應強制重新計算並更新快取。
53+
/// </summary>
54+
[Fact]
55+
public void TryBeginDpiLayout_ForceRecalculate_ReturnsTrueEvenIfSameDpi()
56+
{
57+
float lastDpi = 96f;
58+
bool result = DialogLayoutHelper.TryBeginDpiLayout(96f, ref lastDpi, forceRecalculate: true);
59+
60+
Assert.True(result);
61+
Assert.Equal(96f, lastDpi);
62+
}
63+
64+
/// <summary>
65+
/// DPI 從高降至低也應觸發重新計算(確保縮小場景同樣被偵測)。
66+
/// </summary>
67+
[Fact]
68+
public void TryBeginDpiLayout_DpiDecreased_ReturnsTrueAndUpdatesCache()
69+
{
70+
float lastDpi = 144f;
71+
bool result = DialogLayoutHelper.TryBeginDpiLayout(96f, ref lastDpi);
72+
73+
Assert.True(result);
74+
Assert.Equal(96f, lastDpi);
75+
}
76+
77+
// ── GetMaxFitSize ──────────────────────────────────────────────────────
78+
79+
/// <summary>
80+
/// 一般工作區應回傳 workArea 扣除 margin 的寬高。
81+
/// </summary>
82+
[Fact]
83+
public void GetMaxFitSize_NormalWorkArea_ReturnsWidthAndHeightMinusMargin()
84+
{
85+
Rectangle workArea = new(0, 0, 1920, 1080);
86+
var (w, h) = DialogLayoutHelper.GetMaxFitSize(workArea, margin: 40);
87+
88+
Assert.Equal(1880, w);
89+
Assert.Equal(1040, h);
90+
}
91+
92+
/// <summary>
93+
/// 不傳 margin 時應使用預設值 40,結果相同。
94+
/// </summary>
95+
[Fact]
96+
public void GetMaxFitSize_DefaultMargin_Is40()
97+
{
98+
Rectangle workArea = new(0, 0, 800, 600);
99+
var (w, h) = DialogLayoutHelper.GetMaxFitSize(workArea);
100+
101+
Assert.Equal(760, w);
102+
Assert.Equal(560, h);
103+
}
104+
105+
/// <summary>
106+
/// 工作區寬度小於或等於 margin 時,MaxFitWidth 應回傳最小值 1,不允許 &lt;= 0。
107+
/// </summary>
108+
[Fact]
109+
public void GetMaxFitSize_WorkAreaSmallerThanMargin_ReturnsAtLeastOne()
110+
{
111+
Rectangle workArea = new(0, 0, 30, 30);
112+
var (w, h) = DialogLayoutHelper.GetMaxFitSize(workArea, margin: 40);
113+
114+
Assert.Equal(1, w);
115+
Assert.Equal(1, h);
116+
}
117+
118+
/// <summary>
119+
/// margin = 0 時回傳值應等於工作區的實際寬高。
120+
/// </summary>
121+
[Fact]
122+
public void GetMaxFitSize_ZeroMargin_ReturnsExactWorkAreaSize()
123+
{
124+
Rectangle workArea = new(0, 0, 1280, 720);
125+
var (w, h) = DialogLayoutHelper.GetMaxFitSize(workArea, margin: 0);
126+
127+
Assert.Equal(1280, w);
128+
Assert.Equal(720, h);
129+
}
130+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using InputBox.Core.Utilities;
2+
using Xunit;
3+
4+
namespace InputBox.Tests;
5+
6+
/// <summary>
7+
/// InputBoxLayoutManager.UpdateMinimumSize DPI 防抖邏輯測試
8+
/// </summary>
9+
public class InputBoxLayoutManagerTests
10+
{
11+
// ── UpdateMinimumSize ──────────────────────────────────────────────────
12+
13+
/// <summary>
14+
/// DPI 沒有變化(差值 &lt; 0.01)時,不應呼叫任何回呼,並回傳原始快取值。
15+
/// </summary>
16+
[Fact]
17+
public void UpdateMinimumSize_SameDpi_DoesNotInvokeCallbacks()
18+
{
19+
bool layoutCalled = false;
20+
bool opacityCalled = false;
21+
22+
float result = InputBoxLayoutManager.UpdateMinimumSize(
23+
currentDpi: 96f,
24+
lastAppliedDpi: 96f,
25+
updateLayoutConstraints: () => layoutCalled = true,
26+
updateOpacityWhenHighContrast: () => opacityCalled = true);
27+
28+
Assert.False(layoutCalled);
29+
Assert.False(opacityCalled);
30+
Assert.Equal(96f, result);
31+
}
32+
33+
/// <summary>
34+
/// DPI 在 0.01 閾值以內的微小差距不應觸發更新。
35+
/// </summary>
36+
[Fact]
37+
public void UpdateMinimumSize_TinyDiffBelowThreshold_DoesNotInvokeCallbacks()
38+
{
39+
bool layoutCalled = false;
40+
41+
float result = InputBoxLayoutManager.UpdateMinimumSize(
42+
currentDpi: 96.005f,
43+
lastAppliedDpi: 96f,
44+
updateLayoutConstraints: () => layoutCalled = true,
45+
updateOpacityWhenHighContrast: () => { });
46+
47+
Assert.False(layoutCalled);
48+
// 快取不應被更新(小於閾值回傳 lastAppliedDpi)
49+
Assert.Equal(96f, result);
50+
}
51+
52+
/// <summary>
53+
/// DPI 有明確變化時,應呼叫 updateLayoutConstraints 回呼並回傳新的 DPI 值。
54+
/// </summary>
55+
[Fact]
56+
public void UpdateMinimumSize_ChangedDpi_InvokesLayoutCallback()
57+
{
58+
bool layoutCalled = false;
59+
60+
float result = InputBoxLayoutManager.UpdateMinimumSize(
61+
currentDpi: 120f,
62+
lastAppliedDpi: 96f,
63+
updateLayoutConstraints: () => layoutCalled = true,
64+
updateOpacityWhenHighContrast: () => { });
65+
66+
Assert.True(layoutCalled);
67+
Assert.Equal(120f, result);
68+
}
69+
70+
/// <summary>
71+
/// DPI 從高降至低也應觸發 updateLayoutConstraints(縮小場景同樣被偵測)。
72+
/// </summary>
73+
[Fact]
74+
public void UpdateMinimumSize_DpiDecreased_InvokesLayoutCallback()
75+
{
76+
bool layoutCalled = false;
77+
78+
float result = InputBoxLayoutManager.UpdateMinimumSize(
79+
currentDpi: 96f,
80+
lastAppliedDpi: 144f,
81+
updateLayoutConstraints: () => layoutCalled = true,
82+
updateOpacityWhenHighContrast: () => { });
83+
84+
Assert.True(layoutCalled);
85+
Assert.Equal(96f, result);
86+
}
87+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using InputBox.Core.Feedback;
2+
using Xunit;
3+
4+
namespace InputBox.Tests;
5+
6+
/// <summary>
7+
/// VibrationPatterns 的靜態欄位值與 GlobalIntensityMultiplier 行為測試
8+
/// <para>確保各震動設定的強度與持續時間符合設計規格,且全域倍率能正確設定與讀取。</para>
9+
/// </summary>
10+
public class VibrationPatternsTests
11+
{
12+
// ── GlobalIntensityMultiplier ──────────────────────────────────────────
13+
14+
/// <summary>
15+
/// GlobalIntensityMultiplier 預設值應為 0.7f。
16+
/// </summary>
17+
[Fact]
18+
public void GlobalIntensityMultiplier_DefaultValue_Is0Point7()
19+
{
20+
// 重置為已知狀態
21+
VibrationPatterns.GlobalIntensityMultiplier = 0.7f;
22+
Assert.Equal(0.7f, VibrationPatterns.GlobalIntensityMultiplier);
23+
}
24+
25+
/// <summary>
26+
/// 設定 GlobalIntensityMultiplier 後,讀取值應與設定值一致。
27+
/// </summary>
28+
[Fact]
29+
public void GlobalIntensityMultiplier_SetAndGet_ReturnsNewValue()
30+
{
31+
float original = VibrationPatterns.GlobalIntensityMultiplier;
32+
33+
try
34+
{
35+
VibrationPatterns.GlobalIntensityMultiplier = 0.5f;
36+
Assert.Equal(0.5f, VibrationPatterns.GlobalIntensityMultiplier);
37+
}
38+
finally
39+
{
40+
VibrationPatterns.GlobalIntensityMultiplier = original;
41+
}
42+
}
43+
44+
// ── 靜態欄位設計值 ─────────────────────────────────────────────────────
45+
46+
/// <summary>
47+
/// CursorMove 強度應為 18000(足以克服馬達啟動閾值的輕微點擊感)。
48+
/// </summary>
49+
[Fact]
50+
public void CursorMove_Strength_Is18000()
51+
{
52+
Assert.Equal(18000, VibrationPatterns.CursorMove.Strength);
53+
}
54+
55+
/// <summary>
56+
/// CursorMove 持續時間應為 50ms(短暫點擊感,避免連按時糊在一起)。
57+
/// </summary>
58+
[Fact]
59+
public void CursorMove_Duration_Is50()
60+
{
61+
Assert.Equal(50, VibrationPatterns.CursorMove.Duration);
62+
}
63+
64+
/// <summary>
65+
/// CopySuccess 強度應為 40000(約 60% 強度的確認感)。
66+
/// </summary>
67+
[Fact]
68+
public void CopySuccess_Strength_Is40000()
69+
{
70+
Assert.Equal(40000, VibrationPatterns.CopySuccess.Strength);
71+
}
72+
73+
/// <summary>
74+
/// CopySuccess 持續時間應為 150ms。
75+
/// </summary>
76+
[Fact]
77+
public void CopySuccess_Duration_Is150()
78+
{
79+
Assert.Equal(150, VibrationPatterns.CopySuccess.Duration);
80+
}
81+
82+
/// <summary>
83+
/// ActionFail 強度應為 45000(最強,確保錯誤不被忽視)。
84+
/// </summary>
85+
[Fact]
86+
public void ActionFail_Strength_Is45000()
87+
{
88+
Assert.Equal(45000, VibrationPatterns.ActionFail.Strength);
89+
}
90+
91+
/// <summary>
92+
/// ActionFail 持續時間應為 200ms(最長,打破節奏)。
93+
/// </summary>
94+
[Fact]
95+
public void ActionFail_Duration_Is200()
96+
{
97+
Assert.Equal(200, VibrationPatterns.ActionFail.Duration);
98+
}
99+
100+
/// <summary>
101+
/// ControllerConnected 強度應為 30000(觸覺握手的中等強度)。
102+
/// </summary>
103+
[Fact]
104+
public void ControllerConnected_Strength_Is30000()
105+
{
106+
Assert.Equal(30000, VibrationPatterns.ControllerConnected.Strength);
107+
}
108+
109+
/// <summary>
110+
/// ControllerConnected 持續時間應為 200ms(確保視聽雙障使用者也能感知)。
111+
/// </summary>
112+
[Fact]
113+
public void ControllerConnected_Duration_Is200()
114+
{
115+
Assert.Equal(200, VibrationPatterns.ControllerConnected.Duration);
116+
}
117+
118+
// ── VibrationProfile 值語義 ────────────────────────────────────────────
119+
120+
/// <summary>
121+
/// VibrationProfile 的 Strength 與 Duration 應正確封裝初始值。
122+
/// </summary>
123+
[Fact]
124+
public void VibrationProfile_ConstructorValues_AreAccessible()
125+
{
126+
var profile = new VibrationProfile(12345, 99);
127+
Assert.Equal(12345, profile.Strength);
128+
Assert.Equal(99, profile.Duration);
129+
}
130+
131+
/// <summary>
132+
/// VibrationProfile 是 record struct,相同值的兩個實例應相等。
133+
/// </summary>
134+
[Fact]
135+
public void VibrationProfile_SameValues_AreEqual()
136+
{
137+
var a = new VibrationProfile(30000, 120);
138+
var b = new VibrationProfile(30000, 120);
139+
Assert.Equal(a, b);
140+
}
141+
142+
/// <summary>
143+
/// VibrationProfile 不同值的兩個實例應不相等。
144+
/// </summary>
145+
[Fact]
146+
public void VibrationProfile_DifferentValues_AreNotEqual()
147+
{
148+
var a = new VibrationProfile(30000, 120);
149+
var b = new VibrationProfile(30000, 121);
150+
Assert.NotEqual(a, b);
151+
}
152+
}

0 commit comments

Comments
 (0)