Ground the 21-day habit myth in actual research (Lally et al. 2010) and let users pick a target period (21/66/254 days or unlimited) matching that study's easy/median/hard automaticity timelines when creating a habit, with progress shown on the habits list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from app.models.habit import ALL_WEEKDAYS_MASK, Habit, HabitDifficulty, HabitType
|
|
from app.template_utils import difficulty_label, goal_progress, heatmap_opacity, weekday_label
|
|
|
|
|
|
def _habit_created_days_ago(days_ago: int, difficulty: HabitDifficulty) -> Habit:
|
|
return Habit(
|
|
name="테스트",
|
|
habit_type=HabitType.BUILD,
|
|
difficulty=difficulty,
|
|
weekdays_mask=ALL_WEEKDAYS_MASK,
|
|
created_at=datetime.now() - timedelta(days=days_ago),
|
|
)
|
|
|
|
|
|
def test_weekday_label_all_days_shows_daily():
|
|
assert weekday_label(ALL_WEEKDAYS_MASK) == "매일"
|
|
|
|
|
|
def test_weekday_label_lists_selected_days_in_order():
|
|
monday_and_wednesday = 0b0000001 | 0b0000100
|
|
assert weekday_label(monday_and_wednesday) == "월, 수"
|
|
|
|
|
|
def test_weekday_label_no_days_selected():
|
|
assert weekday_label(0) == "선택된 요일 없음"
|
|
|
|
|
|
def test_heatmap_opacity_zero_when_nothing_scheduled():
|
|
assert heatmap_opacity(checked_count=0, scheduled_count=0) == 0.0
|
|
|
|
|
|
def test_heatmap_opacity_zero_when_nothing_checked():
|
|
assert heatmap_opacity(checked_count=0, scheduled_count=5) == 0.0
|
|
|
|
|
|
def test_heatmap_opacity_full_ratio_caps_at_point_nine():
|
|
assert heatmap_opacity(checked_count=5, scheduled_count=5) == 0.9
|
|
|
|
|
|
def test_heatmap_opacity_partial_ratio():
|
|
assert heatmap_opacity(checked_count=1, scheduled_count=2) == round(0.12 + 0.5 * 0.78, 2)
|
|
|
|
|
|
def test_difficulty_label_maps_to_target_days():
|
|
assert difficulty_label(HabitDifficulty.EASY) == "하 (21일)"
|
|
assert difficulty_label(HabitDifficulty.MEDIUM) == "중 (66일)"
|
|
assert difficulty_label(HabitDifficulty.HARD) == "상 (254일)"
|
|
assert difficulty_label(HabitDifficulty.UNLIMITED) == "기간 무제한"
|
|
|
|
|
|
def test_goal_progress_none_for_unlimited():
|
|
habit = _habit_created_days_ago(10, HabitDifficulty.UNLIMITED)
|
|
assert goal_progress(habit) is None
|
|
|
|
|
|
def test_goal_progress_day_one_on_creation_day():
|
|
habit = _habit_created_days_ago(0, HabitDifficulty.EASY)
|
|
progress = goal_progress(habit)
|
|
assert progress == {"elapsed": 1, "target": 21, "remaining": 20, "reached": False}
|
|
|
|
|
|
def test_goal_progress_mid_period():
|
|
habit = _habit_created_days_ago(10, HabitDifficulty.MEDIUM)
|
|
progress = goal_progress(habit)
|
|
assert progress == {"elapsed": 11, "target": 66, "remaining": 55, "reached": False}
|
|
|
|
|
|
def test_goal_progress_reached_caps_elapsed_at_target():
|
|
habit = _habit_created_days_ago(100, HabitDifficulty.EASY)
|
|
progress = goal_progress(habit)
|
|
assert progress == {"elapsed": 21, "target": 21, "remaining": 0, "reached": True}
|
|
|
|
|
|
def test_target_days_and_goal_target_date_on_model():
|
|
habit = _habit_created_days_ago(0, HabitDifficulty.EASY)
|
|
assert habit.target_days == 21
|
|
assert habit.goal_target_date == habit.created_at.date() + timedelta(days=20)
|
|
|
|
unlimited = _habit_created_days_ago(0, HabitDifficulty.UNLIMITED)
|
|
assert unlimited.target_days is None
|
|
assert unlimited.goal_target_date is None
|