refactor(services): extract pure helpers and EffectSink trait (F23)#31
Merged
Conversation
… the dark corners (F23) Sub-branch 8 of the v0.7.0 hardening epic. Resolves "测不动" for the three service files that have been 0% covered since v0.6 because they cfg-out their entire module body in the test build (#[cfg(not(test))]). User directive: "调架构让可测" (rearchitect for testability) — taken seriously but without speculative abstraction. Per CLAUDE.md "Abstract on evidence": only the seams that *need* a fake get one; the rest is pure-helper extraction. What changed: 1. timer/effect_executor.rs — introduce `EffectSink` trait (9 methods, one per Effect variant) + free `execute_effect(Option<&dyn EffectSink>, &Effect)` dispatcher. ServiceContext now implements EffectSink in the production cfg. Tests use a RecordingSink to assert effect-to-call mapping. 0% to 100%. 2. services/context.rs — extract `pick_suppression_reason` (priority resolver over the four SkipFlags) and `make_stat_queue_overflow_payload` (event payload constructor) as free functions compiled in both prod and test builds. Both were previously buried inside the #[cfg(not(test))] dispatch and untestable. 0% to 90.56% lines, 0% to 86.67% functions. 3. services/tray_tooltip.rs (NEW) — pure i18n + state to tooltip string rendering, lifted out of TrayService::render_tooltip. Compiles in test builds (no Tauri dep) and is covered 99% / 100% functions. 4. services/window_layout.rs (NEW) — pure multi-monitor primary-selection rule, tip-window label scheme, and URL selector lifted out of WindowService::show_tip_windows. 100% / 100%. What's NOT done (honest gap): - tray.rs and window.rs themselves stay #[cfg(not(test))] cfg-out at the module level; their remaining ~600 lines are pure Tauri-API glue (MenuItem / TrayIcon / WebviewWindowBuilder) that can't be unit-tested without introducing a full port adapter. Those are scheduled for a follow-up PR. This PR covers the logic in those services by extraction into pure sibling modules. Coverage gate (cargo llvm-cov --fail-under-lines 80) passes: TOTAL 86.68% lines / 82.14% functions (was 85.32% / 79.86%). No file regressed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 23, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sub-branch 8 of the v0.7.0 hardening epic. Resolves F23 (untestable services) per the user's directive "调架构让可测" — rearchitect for testability rather than accept a coverage exemption.
Three of the previously 0% covered files (context.rs, timer/effect_executor.rs) plus the testable parts of tray.rs / window.rs are now ≥90% line / ≥86% function covered. The backend coverage gate is green at 86.68% lines / 82.14% functions project-wide (up from 85.32% / 79.86%), no file regressed.
What changed
New abstraction (only where evidence demanded it)
EffectSink trait in services/timer/effect_executor.rs — 9 methods, one per Effect variant. The free execute_effect dispatcher matches the variant and calls the appropriate method. ServiceContext implements EffectSink in the production cfg. Tests inject a RecordingSink that captures structured Call enums for assertion. One method per variant on purpose: a single fn dispatch(&Effect) would push the match into every fake (CLAUDE.md "no flag-driven branching").
Pure-helper extractions (no trait, no abstraction — just code that compiles in test build)
Honest gap
tray.rs (537 lines) and window.rs (188 lines) stay #[cfg(not(test))] at the module level. Their remaining bodies after this PR are only Tauri-API glue (MenuItem::with_id, TrayIconBuilder, WebviewWindowBuilder, monitor enumeration, set_position, etc.) — code that legitimately needs a WindowPort / TrayPort adapter to fake. Building those ports + adapters is a follow-up PR.
Coverage delta
No coverage exclusion list changes were necessary — the previously 0% files were not on any exclusion list, they were simply #[cfg(not(test))]-gated out of the test build. The cfg-gating remains for the remaining Tauri-glue parts; the now-testable logic lives in sibling modules without cfg gates.
Tests added
Total: +33 Rust tests, 227 passing (was 194), 0 failing.
Honest completion status
Test plan
Constraints honored
🤖 Generated with Claude Code