From 1366aa1edccdc99f6fa1cbb812286f8c15ecc1df Mon Sep 17 00:00:00 2001 From: VailElla <63608800+VailElla@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:27:09 +0800 Subject: [PATCH] Release AutoPowerMode v1.3.3 --- .github/workflows/ci.yml | 36 ++++++ .github/workflows/release.yml | 4 + AutoPowerMode.sln | 7 ++ CHANGELOG.md | 8 ++ README.md | 7 +- README.zh-CN.md | 7 +- src/AutoPowerMode/AppInfo.cs | 2 +- src/AutoPowerMode/AutoPowerMode.csproj | 6 +- src/AutoPowerMode/UI/SettingsForm.cs | 24 +++- .../AutoPowerMode.WindowsUi.Tests.csproj | 15 +++ .../AutoPowerMode.WindowsUi.Tests/Program.cs | 117 ++++++++++++++++++ 11 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj create mode 100644 tests/AutoPowerMode.WindowsUi.Tests/Program.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ab9fa31 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Windows CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: windows-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@v7 + + - name: Set up .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 8.0.x + + - name: Verify formatting + run: dotnet format AutoPowerMode.sln --verify-no-changes + + - name: Run logic tests + run: dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj --configuration Release + + - name: Run Windows Settings UI smoke test + run: dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj --configuration Release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76d4827..2ba876a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,10 @@ jobs: shell: pwsh run: dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj --configuration Release + - name: Run Windows Settings UI smoke test + shell: pwsh + run: dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj --configuration Release + - name: Publish framework-dependent Windows app shell: pwsh run: >- diff --git a/AutoPowerMode.sln b/AutoPowerMode.sln index 35bfff7..678b087 100644 --- a/AutoPowerMode.sln +++ b/AutoPowerMode.sln @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{885E9D21 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPowerMode.Tests", "tests\AutoPowerMode.Tests\AutoPowerMode.Tests.csproj", "{25EA1EF4-9A28-41A0-867E-78E71FCBA47B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPowerMode.WindowsUi.Tests", "tests\AutoPowerMode.WindowsUi.Tests\AutoPowerMode.WindowsUi.Tests.csproj", "{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,9 +30,14 @@ Global {25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Debug|Any CPU.Build.0 = Debug|Any CPU {25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Release|Any CPU.ActiveCfg = Release|Any CPU {25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Release|Any CPU.Build.0 = Release|Any CPU + {1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {2063FF39-BF7A-4775-B8B2-4176944BFABC} = {BBAC319F-8D99-4F35-A6E2-163EAF32C29C} {25EA1EF4-9A28-41A0-867E-78E71FCBA47B} = {885E9D21-A0F5-4333-A557-CC623A1922A0} + {1AD8DC52-50DF-45C3-88D2-CE8047C396DD} = {885E9D21-A0F5-4333-A557-CC623A1922A0} EndGlobalSection EndGlobal diff --git a/CHANGELOG.md b/CHANGELOG.md index e3429c3..c70e124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # AutoPowerMode Changelog +## [v1.3.3] - 2026-07-12 + +- 修复 v1.3.2 在 Windows 打开设置窗口时可能抛出 `TableLayoutPanel` 已满异常的问题:响应式文字宽度和控件尺寸现在会在暂停子布局期间一次性更新,并允许布局表在 WinForms 需要时安全扩展行。 +- 新增 Windows 专用设置窗口冒烟测试,实际打开中英文设置页并在宽、窄窗口间反复缩放,同时确认不会出现横向滚动;PR 和正式发布流程都会在 Windows runner 上执行该测试。 + +- Fixed a v1.3.2 startup error that could occur when opening Settings on Windows because a full fixed-size `TableLayoutPanel` relaid itself while responsive text constraints were still being updated. Child layout updates are now applied as one suspended batch, and the table can safely add a row if WinForms requires one. +- Added a Windows-only Settings UI smoke test that opens both localized forms, repeatedly resizes between wide and narrow layouts, and verifies that horizontal scrolling stays hidden. The test now gates both pull requests and releases on a Windows runner. + ## [v1.3.2] - 2026-07-12 - 修复设置窗口缩小后仍出现横向滚动和左侧内容被裁切的问题:窄窗口自动切换为上下排列,长状态和选项文字可换行,并为纵向滚动条预留宽度;底部操作栏继续固定显示。 diff --git a/README.md b/README.md index 664ef39..49ad31a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # AutoPowerMode -Current version: v1.3.2 +Current version: v1.3.3 AutoPowerMode is a Windows-only system tray application that switches Windows power plans automatically according to whether the current user is actively using the computer. @@ -50,6 +50,7 @@ src/AutoPowerMode/ Application source Services/ Windows and local-system services UI/ Tray, settings, and diagnostics UI tests/AutoPowerMode.Tests/ Logic tests +tests/AutoPowerMode.WindowsUi.Tests/ Windows-only Settings UI smoke test docs/reviews/ Historical release reviews archive/ Git-ignored local build history ``` @@ -59,6 +60,8 @@ archive/ Git-ignored local build history ```bash dotnet build AutoPowerMode.sln --configuration Release dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj +# Windows only: +dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj ``` -The tests cover configuration migration, independently configurable dual-rate monitoring, idle protections, language selection and persistence, power-plan parsing, switch policies, notifications, DPI layout, log rotation and path sanitization, startup registration, and external override protection. +The logic tests cover configuration migration, independently configurable dual-rate monitoring, idle protections, language selection and persistence, power-plan parsing, switch policies, notifications, DPI layout, log rotation and path sanitization, startup registration, and external override protection. The Windows-only smoke test opens the localized Settings form and exercises both wide and compact layouts. diff --git a/README.zh-CN.md b/README.zh-CN.md index a3c1e30..964e6e6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,7 +2,7 @@ # AutoPowerMode -当前版本:v1.3.2 +当前版本:v1.3.3 AutoPowerMode 是一个 Windows-only 桌面托盘程序,用于根据当前用户是否正在使用电脑,自动切换 Windows 电源计划。 @@ -50,6 +50,7 @@ src/AutoPowerMode/ 应用源码 Services/ Windows 和本地系统服务 UI/ 托盘、设置和诊断界面 tests/AutoPowerMode.Tests/ 纯逻辑自动测试 +tests/AutoPowerMode.WindowsUi.Tests/ Windows 专用设置窗口冒烟测试 docs/reviews/ 历史版本审查记录 archive/ 被 Git 忽略的本地构建历史 ``` @@ -59,6 +60,8 @@ archive/ 被 Git 忽略的本地构建历史 ```bash dotnet build AutoPowerMode.sln --configuration Release dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj +# 仅限 Windows: +dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj ``` -测试覆盖配置迁移、可独立配置的双频检测策略、空闲误触保护、语言选择与持久化、电源计划解析、切换策略、通知、DPI 布局、日志轮转与路径脱敏、开机自启和外部覆盖保护。 +逻辑测试覆盖配置迁移、可独立配置的双频检测策略、空闲误触保护、语言选择与持久化、电源计划解析、切换策略、通知、DPI 布局、日志轮转与路径脱敏、开机自启和外部覆盖保护。Windows 专用冒烟测试会实际打开中英文设置窗口并验证宽窄布局切换。 diff --git a/src/AutoPowerMode/AppInfo.cs b/src/AutoPowerMode/AppInfo.cs index 5eea745..ca46daf 100644 --- a/src/AutoPowerMode/AppInfo.cs +++ b/src/AutoPowerMode/AppInfo.cs @@ -3,7 +3,7 @@ namespace AutoPowerMode; public static class AppInfo { public const string Name = "AutoPowerMode"; - public const string Version = "v1.3.2"; + public const string Version = "v1.3.3"; public const string DisplayName = $"{Name} {Version}"; public const string RepositoryUrl = "https://github.com/VailElla/AutoPowerMode"; } diff --git a/src/AutoPowerMode/AutoPowerMode.csproj b/src/AutoPowerMode/AutoPowerMode.csproj index e8a5585..4f45d82 100644 --- a/src/AutoPowerMode/AutoPowerMode.csproj +++ b/src/AutoPowerMode/AutoPowerMode.csproj @@ -9,9 +9,9 @@ enable AutoPowerMode AutoPowerMode - 1.3.2 - 1.3.2.0 - 1.3.2.0 + 1.3.3 + 1.3.3.0 + 1.3.3.0 diff --git a/src/AutoPowerMode/UI/SettingsForm.cs b/src/AutoPowerMode/UI/SettingsForm.cs index 5eb88d9..4b0278a 100644 --- a/src/AutoPowerMode/UI/SettingsForm.cs +++ b/src/AutoPowerMode/UI/SettingsForm.cs @@ -84,7 +84,7 @@ private void BuildLayout(IReadOnlyList powerPlans) root.AutoSizeMode = AutoSizeMode.GrowAndShrink; root.Padding = new Padding(12, 12, 12, 6); root.Margin = Padding.Empty; - root.GrowStyle = TableLayoutPanelGrowStyle.FixedSize; + root.GrowStyle = TableLayoutPanelGrowStyle.AddRows; ConfigureTextLabel(_automationStatusLabel, LocalizationService.Text("AutomationStatus")); ConfigureTextLabel(_idleThresholdLabel, LocalizationService.Text("IdleSwitchDelay")); @@ -403,12 +403,24 @@ private Size LayoutSettingsAtWidth(int requestedWidth) { var contentWidth = Math.Max(1, requestedWidth); var compact = DpiLayoutPolicy.ShouldUseCompactSettingsLayout(DeviceDpi, contentWidth); - ArrangeSettingsControls(compact); - ConfigureWrappingConstraints(contentWidth, compact); - _root.MinimumSize = new Size(contentWidth, 0); - _root.MaximumSize = new Size(contentWidth, 0); - _root.Width = contentWidth; + _root.SuspendLayout(); + _idleProtectionOptions.SuspendLayout(); + try + { + ArrangeSettingsControls(compact); + ConfigureWrappingConstraints(contentWidth, compact); + + _root.MinimumSize = new Size(contentWidth, 0); + _root.MaximumSize = new Size(contentWidth, 0); + _root.Width = contentWidth; + } + finally + { + _idleProtectionOptions.ResumeLayout(performLayout: false); + _root.ResumeLayout(performLayout: false); + } + _root.PerformLayout(); var preferredSize = _root.GetPreferredSize(new Size(contentWidth, 0)); diff --git a/tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj b/tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj new file mode 100644 index 0000000..860fa01 --- /dev/null +++ b/tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj @@ -0,0 +1,15 @@ + + + Exe + net8.0-windows + true + true + enable + enable + AutoPowerMode.WindowsUi.Tests + + + + + + diff --git a/tests/AutoPowerMode.WindowsUi.Tests/Program.cs b/tests/AutoPowerMode.WindowsUi.Tests/Program.cs new file mode 100644 index 0000000..6c71571 --- /dev/null +++ b/tests/AutoPowerMode.WindowsUi.Tests/Program.cs @@ -0,0 +1,117 @@ +using AutoPowerMode; +using System.Drawing; +using System.Windows.Forms; + +namespace AutoPowerMode.WindowsUi.Tests; + +internal static class Program +{ + [STAThread] + private static int Main() + { + try + { + ApplicationConfiguration.Initialize(); + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException); + + VerifySettingsLayout(AppLanguagePreference.SimplifiedChinese); + VerifySettingsLayout(AppLanguagePreference.English); + + Console.WriteLine("PASS SettingsForm opens and resizes without horizontal scrolling."); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine("FAIL SettingsForm Windows UI smoke test."); + Console.Error.WriteLine(ex); + return 1; + } + } + + private static void VerifySettingsLayout(string languagePreference) + { + LocalizationService.UsePreference(languagePreference); + + var plans = new[] + { + new PowerPlan + { + Guid = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c", + Name = "High Performance", + IsActive = true + }, + new PowerPlan + { + Guid = "a1841308-3541-4fab-bc81-f71556f20b4a", + Name = "Power Saver" + } + }; + var config = new AppConfig + { + IdleThresholdSeconds = 1000, + ActiveCheckIntervalSeconds = 30, + IdleCheckIntervalSeconds = 1, + ActivePowerPlanGuid = plans[0].Guid, + IdlePowerPlanGuid = plans[1].Guid, + NotificationsEnabled = true, + Language = languagePreference, + PowerPlansConfiguredByUser = true + }; + + using var form = new SettingsForm( + config, + plans, + LocalizationService.Text("StatusWaiting")) + { + ShowInTaskbar = false + }; + + form.Show(); + ProcessPendingLayout(form); + + foreach (var clientSize in new[] + { + new Size(700, 520), + new Size(400, 240), + new Size(460, 270), + new Size(900, 600) + }) + { + form.ClientSize = clientSize; + ProcessPendingLayout(form); + + var scrollPanel = Descendants(form) + .OfType() + .Single(control => control.AutoScroll); + if (scrollPanel.HorizontalScroll.Visible) + { + throw new InvalidOperationException( + $"Horizontal scrolling appeared at {form.ClientSize.Width}x{form.ClientSize.Height}."); + } + } + + form.Close(); + Application.DoEvents(); + } + + private static void ProcessPendingLayout(Control control) + { + control.PerformLayout(); + Application.DoEvents(); + control.PerformLayout(); + Application.DoEvents(); + } + + private static IEnumerable Descendants(Control parent) + { + foreach (Control child in parent.Controls) + { + yield return child; + + foreach (var descendant in Descendants(child)) + { + yield return descendant; + } + } + } +}