Files
TimeTracker/tests/test_support_services.py
T
Dries Peeters b0dde80ba9 feat(web): high-visibility support modal, prompts, and supporter UX
Add a support modal with usage stats, tier and license links, share control, and offline-safe outbound CTAs. Surface support from the header, sidebar, user menu, dashboard card, and settings "Support & Community" section without hiding entry points when a supporter license is active.

Introduce UsageStatsService and a persisted users.support_stats_reports_generated counter incremented on key report exports and custom report views. Add SupportPromptService for session-scoped soft toasts (after export, dashboard milestones, long session via POST /donate/request-soft-prompt).

Wire consent-aware track_event names support.* and mirror funnel rows in DonationInteraction; fix has_recent_donation_click to treat link_clicked as a recent click. Document events and SUPPORT_* / migration notes in docs.

Tests: tests/test_support_services.py for prompt and usage stats behavior.
2026-04-15 10:55:37 +02:00

64 lines
2.2 KiB
Python

"""Tests for support prompt and usage stats services."""
from app.services.support_prompt_service import SupportPromptService
from app.services.usage_stats_service import UsageStatsService
def test_usage_stats_service_shape(app, test_user):
with app.app_context():
s = UsageStatsService.get_for_user(test_user.id)
assert "total_hours" in s
assert "time_entries_count" in s
assert "days_since_signup" in s
assert "reports_generated_count" in s
def test_consume_layout_prompt_sets_consumed():
session = {"support_prompt_trigger": SupportPromptService.VARIANT_AFTER_REPORT}
payload = SupportPromptService.consume_layout_prompt(
session,
ui_show_donate=True,
is_supporter=False,
support_banner_suppressed=False,
)
assert payload is not None
assert payload.get("variant") == SupportPromptService.VARIANT_AFTER_REPORT
assert session.get(SupportPromptService.SESSION_SOFT_PROMPT_CONSUMED) is True
assert "support_prompt_trigger" not in session
def test_support_prompt_suppressed_for_supporter():
session = {"support_prompt_trigger": SupportPromptService.VARIANT_AFTER_REPORT}
payload = SupportPromptService.consume_layout_prompt(
session,
ui_show_donate=True,
is_supporter=True,
support_banner_suppressed=False,
)
assert payload is None
def test_support_prompt_respects_ui_show_donate():
session = {"support_prompt_trigger": SupportPromptService.VARIANT_AFTER_REPORT}
payload = SupportPromptService.consume_layout_prompt(
session,
ui_show_donate=False,
is_supporter=False,
support_banner_suppressed=False,
)
assert payload is None
def test_pick_dashboard_skips_when_after_report_pending():
session = {"support_prompt_trigger": SupportPromptService.VARIANT_AFTER_REPORT}
user_stats = {"days_since_signup": 100, "time_entries_count": 1, "total_hours": 1.0}
picked = SupportPromptService.pick_dashboard_prompt(
session,
user_stats,
ui_show_donate=True,
is_supporter=False,
support_banner_suppressed=False,
today_hours=8.0,
)
assert picked is None