feat: US-013 - Frontend - Stats section and weekly summary
This commit is contained in:
@@ -7,6 +7,7 @@ Story US-009: Frontend - Edit habit modal
|
||||
Story US-010: Frontend - Check-in interaction (click and long-press)
|
||||
Story US-011: Frontend - Skip, lives display, and delete confirmation
|
||||
Story US-012: Frontend - Filter and sort controls
|
||||
Story US-013: Frontend - Stats section and weekly summary
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -1288,6 +1289,183 @@ def test_typecheck_us012():
|
||||
assert result == 0, "api.py should pass typecheck (syntax check)"
|
||||
print("✓ Test 77: Typecheck passes")
|
||||
|
||||
# ============================================================================
|
||||
# US-013: Frontend - Stats section and weekly summary
|
||||
# ============================================================================
|
||||
|
||||
def test_stats_section_exists():
|
||||
"""Test 78: Stats section exists with 4 metric cards"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'id="statsSection"' in content, "Should have statsSection element"
|
||||
assert 'class="stats-row"' in content, "Should have stats-row container"
|
||||
assert 'class="stat-card"' in content, "Should have stat-card elements"
|
||||
|
||||
# Check for the 4 metrics
|
||||
assert 'id="statTotalHabits"' in content, "Should have Total Habits metric"
|
||||
assert 'id="statAvgCompletion"' in content, "Should have Avg Completion metric"
|
||||
assert 'id="statBestStreak"' in content, "Should have Best Streak metric"
|
||||
assert 'id="statTotalLives"' in content, "Should have Total Lives metric"
|
||||
|
||||
print("✓ Test 78: Stats section with 4 metric cards exists")
|
||||
|
||||
def test_stats_labels_correct():
|
||||
"""Test 79: Stat cards have correct labels"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'Total Habits' in content, "Should have 'Total Habits' label"
|
||||
assert 'Avg Completion (30d)' in content or 'Avg Completion' in content, \
|
||||
"Should have 'Avg Completion' label"
|
||||
assert 'Best Streak' in content, "Should have 'Best Streak' label"
|
||||
assert 'Total Lives' in content, "Should have 'Total Lives' label"
|
||||
|
||||
print("✓ Test 79: Stat cards have correct labels")
|
||||
|
||||
def test_weekly_summary_exists():
|
||||
"""Test 80: Weekly summary section exists and is collapsible"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'class="weekly-summary"' in content, "Should have weekly-summary section"
|
||||
assert 'class="weekly-summary-header"' in content, "Should have clickable header"
|
||||
assert 'Weekly Summary' in content, "Should have 'Weekly Summary' title"
|
||||
assert 'toggleWeeklySummary()' in content, "Should have toggle function"
|
||||
assert 'id="weeklySummaryContent"' in content, "Should have collapsible content container"
|
||||
assert 'id="weeklySummaryChevron"' in content, "Should have chevron icon"
|
||||
|
||||
print("✓ Test 80: Weekly summary section exists and is collapsible")
|
||||
|
||||
def test_weekly_chart_structure():
|
||||
"""Test 81: Weekly chart displays bars for Mon-Sun"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'id="weeklyChart"' in content, "Should have weeklyChart container"
|
||||
assert 'class="weekly-chart"' in content, "Should have weekly-chart class"
|
||||
|
||||
# Check for bar rendering in JS
|
||||
assert 'weekly-bar' in content, "Should render weekly bars in CSS/JS"
|
||||
assert 'weekly-day-label' in content, "Should have day labels"
|
||||
|
||||
print("✓ Test 81: Weekly chart structure exists")
|
||||
|
||||
def test_weekly_stats_text():
|
||||
"""Test 82: Weekly stats show completed and skipped counts"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'id="weeklyCompletedText"' in content, "Should have completed count element"
|
||||
assert 'id="weeklySkippedText"' in content, "Should have skipped count element"
|
||||
assert 'completed this week' in content, "Should have 'completed this week' text"
|
||||
assert 'skipped this week' in content, "Should have 'skipped this week' text"
|
||||
|
||||
print("✓ Test 82: Weekly stats text elements exist")
|
||||
|
||||
def test_stats_functions_exist():
|
||||
"""Test 83: renderStats and renderWeeklySummary functions exist"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'function renderStats()' in content, "Should have renderStats function"
|
||||
assert 'function renderWeeklySummary()' in content, "Should have renderWeeklySummary function"
|
||||
assert 'function toggleWeeklySummary()' in content, "Should have toggleWeeklySummary function"
|
||||
|
||||
print("✓ Test 83: Stats rendering functions exist")
|
||||
|
||||
def test_stats_calculations():
|
||||
"""Test 84: Stats calculations use client-side logic"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check for total habits calculation
|
||||
assert 'totalHabits' in content, "Should calculate total habits"
|
||||
|
||||
# Check for avg completion calculation
|
||||
assert 'avgCompletion' in content or 'completion_rate_30d' in content, \
|
||||
"Should calculate average completion rate"
|
||||
|
||||
# Check for best streak calculation
|
||||
assert 'bestStreak' in content or 'Math.max' in content, \
|
||||
"Should calculate best streak across all habits"
|
||||
|
||||
# Check for total lives calculation
|
||||
assert 'totalLives' in content or '.lives' in content, \
|
||||
"Should calculate total lives"
|
||||
|
||||
print("✓ Test 84: Stats calculations implemented")
|
||||
|
||||
def test_weekly_chart_bars_proportional():
|
||||
"""Test 85: Weekly chart bars are proportional to completion count"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check that bars use height proportional to count
|
||||
assert 'height' in content and ('style' in content or 'height:' in content), \
|
||||
"Should set bar height dynamically"
|
||||
assert 'maxCompletions' in content or 'Math.max' in content, \
|
||||
"Should calculate max for scaling"
|
||||
|
||||
print("✓ Test 85: Weekly chart bars are proportional")
|
||||
|
||||
def test_stats_called_from_render():
|
||||
"""Test 86: renderStats is called when renderHabits is called"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Find renderHabits function
|
||||
render_habits_start = content.find('function renderHabits()')
|
||||
assert render_habits_start > 0, "renderHabits function should exist"
|
||||
|
||||
# Check that renderStats is called within renderHabits
|
||||
render_habits_section = content[render_habits_start:render_habits_start + 2000]
|
||||
assert 'renderStats()' in render_habits_section, \
|
||||
"renderStats() should be called from renderHabits()"
|
||||
|
||||
print("✓ Test 86: renderStats called from renderHabits")
|
||||
|
||||
def test_stats_css_styling():
|
||||
"""Test 87: Stats section has proper CSS styling"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert '.stats-section' in content, "Should have stats-section CSS"
|
||||
assert '.stats-row' in content, "Should have stats-row CSS"
|
||||
assert '.stat-card' in content, "Should have stat-card CSS"
|
||||
assert '.weekly-summary' in content, "Should have weekly-summary CSS"
|
||||
assert '.weekly-chart' in content, "Should have weekly-chart CSS"
|
||||
assert '.weekly-bar' in content, "Should have weekly-bar CSS"
|
||||
|
||||
print("✓ Test 87: Stats CSS styling exists")
|
||||
|
||||
def test_stats_no_console_errors():
|
||||
"""Test 88: No obvious console error sources in stats code"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check that functions are properly defined
|
||||
assert 'function renderStats()' in content, "renderStats should be defined"
|
||||
assert 'function renderWeeklySummary()' in content, "renderWeeklySummary should be defined"
|
||||
assert 'function toggleWeeklySummary()' in content, "toggleWeeklySummary should be defined"
|
||||
|
||||
# Check DOM element IDs are referenced correctly
|
||||
assert "getElementById('statsSection')" in content or \
|
||||
'getElementById("statsSection")' in content, \
|
||||
"Should reference statsSection element"
|
||||
assert "getElementById('statTotalHabits')" in content or \
|
||||
'getElementById("statTotalHabits")' in content, \
|
||||
"Should reference statTotalHabits element"
|
||||
|
||||
print("✓ Test 88: No obvious console error sources")
|
||||
|
||||
def test_typecheck_us013():
|
||||
"""Test 89: Typecheck passes for api.py"""
|
||||
api_path = Path(__file__).parent.parent / 'api.py'
|
||||
result = os.system(f'python3 -m py_compile {api_path} 2>/dev/null')
|
||||
assert result == 0, "api.py should pass typecheck (syntax check)"
|
||||
print("✓ Test 89: Typecheck passes")
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all tests in sequence"""
|
||||
tests = [
|
||||
@@ -1375,9 +1553,22 @@ def run_all_tests():
|
||||
test_sort_logic_implementation,
|
||||
test_backend_provides_should_check_today,
|
||||
test_typecheck_us012,
|
||||
# US-013 tests
|
||||
test_stats_section_exists,
|
||||
test_stats_labels_correct,
|
||||
test_weekly_summary_exists,
|
||||
test_weekly_chart_structure,
|
||||
test_weekly_stats_text,
|
||||
test_stats_functions_exist,
|
||||
test_stats_calculations,
|
||||
test_weekly_chart_bars_proportional,
|
||||
test_stats_called_from_render,
|
||||
test_stats_css_styling,
|
||||
test_stats_no_console_errors,
|
||||
test_typecheck_us013,
|
||||
]
|
||||
|
||||
print(f"\nRunning {len(tests)} frontend tests for US-006, US-007, US-008, US-009, US-010, US-011, and US-012...\n")
|
||||
print(f"\nRunning {len(tests)} frontend tests for US-006 through US-013...\n")
|
||||
|
||||
failed = []
|
||||
for test in tests:
|
||||
|
||||
Reference in New Issue
Block a user