Skip to content

refactor(services): WindowPort + TrayPort traits to expose tray/window to test build#32

Merged
rsecss merged 1 commit into
mainfrom
refactor/services-ports
May 23, 2026
Merged

refactor(services): WindowPort + TrayPort traits to expose tray/window to test build#32
rsecss merged 1 commit into
mainfrom
refactor/services-ports

Conversation

@rsecss

@rsecss rsecss commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 1 of test/coverage-to-90 sub-branch (#8.5 of v0.7.0 epic). PR #31 left
tray.rs (~537 lines) and window.rs (~162 lines) gated behind
#[cfg(not(test))] in services/mod.rs:10-13, which kept them out of the
test-build coverage pool. The reported 86.68% backend coverage was therefore
measured against a smaller denominator than production reality.

This PR introduces WindowPort and TrayPort traits so both services compile
in the test build, drives them with recording fakes, and pulls the modules out
from behind the cfg gate.

Trait designs

WindowPort (6 methods, one Tauri API each):

Method Purpose
list_monitors() -> Result<Vec<MonitorInfo>> app.available_monitors()
primary_monitor() -> Option<MonitorInfo> app.primary_monitor()
webview_exists(label) -> bool app.get_webview_window(label).is_some()
create_tip_window(spec) -> Result<()> WebviewWindowBuilder + fullscreen-failure-retry fallback
close_window(label) -> Result<()> WebviewWindow::close
list_window_labels() -> Vec<String> app.webview_windows().keys()

TrayPort (8 methods):

Method Purpose
install_tray(labels) -> Result<()> TrayIconBuilder one-shot setup
set_tooltip(text) -> Result<()> tray.set_tooltip
set_menu_text(item, text) -> Result<()> MenuItem::set_text
emit_to_window(window, event, payload) -> Result<()> WebviewWindow::emit
unminimize_window(label) -> Result<()> WebviewWindow::unminimize
show_window(label) -> Result<()> WebviewWindow::show
focus_window(label) -> Result<()> WebviewWindow::set_focus
quit_app() AppHandle::exit(0)

Production adapters TauriWindowPort and TauriTrayPort (in runtime
sub-modules) stay behind #[cfg(not(test))] because they bind directly to
Tauri runtime types. The orchestration code in WindowService and
TrayService no longer touches AppHandle and now compiles unconditionally.
The Tauri-specific tray-panel positioning + click-callback wiring still lives
in runtime (production only) since it's pure Tauri integration with no
testable orchestration left.

services/mod.rs:10-13 lose the #[cfg(not(test))] lines on mod tray; and
mod window;.

Coverage delta

File Lines before Lines after Functions before Functions after
services/window.rs excluded from pool 100.00% excluded 100.00%
services/tray.rs excluded from pool 93.25% excluded 94.94%
TOTAL backend 86.68% (smaller pool) 87.95% (full pool incl. tray + window) 82.14% 84.43%

The denominator grew by ~700 logic lines (tray + window now in the pool) yet
the total ratio went UP because the new tests cover both files well above the
pre-existing average.

Files modified

  • src-tauri/src/services/window.rs — full rewrite (WindowPort trait, TauriWindowPort adapter, 11 orchestration tests)
  • src-tauri/src/services/tray.rs — full rewrite (TrayPort trait, TauriTrayPort adapter, 21 orchestration tests)
  • src-tauri/src/services/mod.rs — drop #[cfg(not(test))] on mod tray / mod window
  • src-tauri/src/services/context.rs — adapt EffectSink to call services.window.show_tip_windows() / services.tray.update_tooltip_best_effort(...) (no longer pass &AppHandle)
  • src-tauri/src/lib.rs — wire TauriWindowPort + TauriTrayPort adapters into service construction

Tests

  • Total: 227 → 259 (+32 new orchestration tests)
  • New services::window::tests: 11 cases (happy path, multi-monitor primary selection, no-primary fallback, no-monitors guard, monitor-enumeration failure, label-already-alive skip, create-error continues, close-error continues, hide filters only tip-window labels, lifecycle no-op)
  • New services::tray::tests: 21 cases (menu-event dispatch including unknown id, initial labels, tooltip happy path + error propagation, pause/resume label flip + error propagation, locale change re-texts every item + tolerates partial failure, show-main-window full sequence + emit/show/focus error short-circuits + unminimize tolerance, quit routes to port, start-with-initial-config installs tray and pushes tooltip + install-error propagation, shutdown without watch, init/start lifecycle no-op without runtime context)

Verification

npm run ci 8 steps all green:

  • [1/8] Version sync check
  • [2/8] Rust format check
  • [3/8] Rust clippy --all-targets -- -D warnings
  • [4/8] Rust tests — 259 passed (default features)
  • [5/8] Frontend type check (svelte-check) — 0 errors
  • [6/8] Frontend tests (vitest --coverage) — gate passes
  • [7/8] Frontend format check (prettier)
  • [8/8] Build frontend (vite build)

Also verified:

  • cargo test --no-default-features — 259 passed
  • cargo llvm-cov --fail-under-lines 80 — passes at 87.95%

Out of scope (NOT in this PR)

The CI gate stays at --fail-under-lines 80 (Rust CI) and lines: 80, functions: 70 (vitest config) on purpose; that gets bumped in a dedicated
Phase 3 PR after Phase 2 has lifted the frontend numbers.

Test plan

  • npm run ci passes locally
  • PR CI matrix passes on Windows / macOS / Linux
  • PR CI runs cargo test --no-default-features job green
  • PR CI cargo-coverage job clears --fail-under-lines 80

🤖 Generated with Claude Code

…cfg(not(test)) gate

Phase 1 of test/coverage-to-90 sub-branch (#8.5 of v0.7.0 epic). PR #31 left
tray.rs (~537 lines) and window.rs (~162 lines) gated behind
`#[cfg(not(test))]` in `services/mod.rs:10-13`, which kept them out of the
test-build coverage pool. The reported 86.68% backend coverage was therefore
measured against a smaller denominator than production reality.

This commit introduces `WindowPort` and `TrayPort` traits that abstract the
Tauri APIs each service depends on:

- `WindowPort` (6 methods): list_monitors, primary_monitor, webview_exists,
  create_tip_window, close_window, list_window_labels
- `TrayPort` (8 methods): install_tray, set_tooltip, set_menu_text,
  emit_to_window, unminimize_window, show_window, focus_window, quit_app

Production adapters `TauriWindowPort` and `TauriTrayPort` (in `runtime`
sub-modules) stay behind `#[cfg(not(test))]` because they bind directly to
Tauri runtime types. The orchestration code in `WindowService` and
`TrayService` no longer touches `AppHandle` and now compiles unconditionally.

`services/mod.rs` loses the cfg gate on `tray` and `window`; the test build
exercises both modules through `FakeWindowPort` / `FakeTrayPort` recording
fakes (Vec<Call> shape, per dispatch instructions).

Coverage results:

  | File                  | Lines (before) | Lines (after) |
  |-----------------------|----------------|----------------|
  | services/window.rs    | excluded       | 100.00%        |
  | services/tray.rs      | excluded       | 93.25%         |
  | TOTAL (backend)       | 86.68%         | 87.95%         |

The denominator grew by ~700 logic lines (tray + window now in pool) yet the
total ratio went UP because the new tests cover both files well above the
existing average.

Test count: 227 -> 259 (+32 new orchestration tests).

Phase 2 (frontend gap fill) and Phase 3 (gate bump to 90/85) are explicit
follow-up dispatches, NOT in this PR. The CI gate stays at `--fail-under-lines
80` (Rust) and `lines: 80, functions: 70` (vitest).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rsecss rsecss merged commit a5e20c4 into main May 23, 2026
6 checks passed
@rsecss rsecss deleted the refactor/services-ports branch May 23, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant