#!/usr/bin/env python3 """ Tests for Story 10.0: Frontend - Check habit interaction """ import sys import re def load_html(): """Load habits.html content""" try: with open('dashboard/habits.html', 'r', encoding='utf-8') as f: return f.read() except FileNotFoundError: print("ERROR: dashboard/habits.html not found") sys.exit(1) def test_checkbox_css_exists(): """Test that checkbox CSS styles are defined""" html = load_html() # Check for checkbox class assert '.habit-checkbox' in html, "Missing .habit-checkbox CSS class" # Check for circular shape (border-radius: 50%) assert 'border-radius: 50%' in html, "Checkbox should be circular (border-radius: 50%)" # Check for checked state assert '.habit-checkbox.checked' in html, "Missing .habit-checkbox.checked CSS" # Check for disabled state assert '.habit-checkbox.disabled' in html, "Missing .habit-checkbox.disabled CSS" # Check for hover state assert '.habit-checkbox:hover' in html, "Missing .habit-checkbox:hover CSS" print("✓ Checkbox CSS styles exist") def test_checkbox_in_habit_card(): """Test that createHabitCard includes checkbox""" html = load_html() # Check that createHabitCard creates a checkbox element assert 'habit-checkbox' in html, "createHabitCard should include checkbox element" # Check for data-habit-id attribute assert 'data-habit-id' in html, "Checkbox should have data-habit-id attribute" # Check for onclick handler assert 'onclick="checkHabit' in html, "Checkbox should have onclick='checkHabit' handler" print("✓ Checkbox is included in habit card") def test_checkbox_checked_state(): """Test that checkbox uses checkedToday to determine state""" html = load_html() # Look for logic that checks habit.checkedToday assert 'checkedToday' in html, "Should check habit.checkedToday property" # Check for conditional checked class assert 'checked' in html, "Should add 'checked' class when checkedToday is true" # Check for check icon assert 'data-lucide="check"' in html, "Should show check icon when checked" print("✓ Checkbox state reflects checkedToday") def test_check_habit_function_exists(): """Test that checkHabit function is defined""" html = load_html() # Check for function definition assert 'function checkHabit' in html or 'async function checkHabit' in html, \ "checkHabit function should be defined" # Check for parameters assert re.search(r'function checkHabit\s*\(\s*habitId', html) or \ re.search(r'async function checkHabit\s*\(\s*habitId', html), \ "checkHabit should accept habitId parameter" print("✓ checkHabit function exists") def test_check_habit_api_call(): """Test that checkHabit calls POST /api/habits/{id}/check""" html = load_html() # Check for fetch call assert 'fetch(' in html, "checkHabit should use fetch API" # Check for POST method assert "'POST'" in html or '"POST"' in html, "checkHabit should use POST method" # Check for /api/habits/ endpoint assert '/api/habits/' in html, "Should call /api/habits/{id}/check endpoint" # Check for /check path assert '/check' in html, "Should call endpoint with /check path" print("✓ checkHabit calls POST /api/habits/{id}/check") def test_optimistic_ui_update(): """Test that checkbox updates immediately (optimistic)""" html = load_html() # Check for classList.add before fetch # The pattern should be: add 'checked' class, then fetch checkHabitFunc = html[html.find('async function checkHabit'):html.find('async function checkHabit') + 2000] # Check for immediate classList modification assert 'classList.add' in checkHabitFunc, "Should add class immediately (optimistic update)" assert "'checked'" in checkHabitFunc or '"checked"' in checkHabitFunc, \ "Should add 'checked' class optimistically" print("✓ Optimistic UI update implemented") def test_error_handling_revert(): """Test that checkbox reverts on error""" html = load_html() # Check for catch block assert 'catch' in html, "checkHabit should have error handling (catch)" # Check for classList.remove in error handler checkHabitFunc = html[html.find('async function checkHabit'):] # Find the catch block if 'catch' in checkHabitFunc: catchBlock = checkHabitFunc[checkHabitFunc.find('catch'):] # Check for revert logic assert 'classList.remove' in catchBlock, "Should revert checkbox on error" assert "'checked'" in catchBlock or '"checked"' in catchBlock, \ "Should remove 'checked' class on error" print("✓ Error handling reverts checkbox") def test_disabled_when_checked(): """Test that checkbox is disabled when already checked""" html = load_html() # Check for disabled class on checked habits assert 'disabled' in html, "Should add 'disabled' class to checked habits" # Check for early return if disabled checkHabitFunc = html[html.find('async function checkHabit'):html.find('async function checkHabit') + 1000] assert 'disabled' in checkHabitFunc, "Should check if checkbox is disabled" assert 'return' in checkHabitFunc, "Should return early if disabled" print("✓ Checkbox disabled when already checked") def test_streak_updates(): """Test that streak updates after successful check""" html = load_html() # Check for streak element ID assert 'streak-' in html, "Should use ID for streak element (e.g., streak-${habit.id})" # Check for getElementById to update streak checkHabitFunc = html[html.find('async function checkHabit'):] assert 'getElementById' in checkHabitFunc or 'getElementById' in html, \ "Should get streak element by ID to update it" # Check that response data is used to update streak assert '.streak' in checkHabitFunc or 'streak' in checkHabitFunc, \ "Should update streak from response data" print("✓ Streak updates after successful check") def test_check_icon_display(): """Test that check icon is shown when checked""" html = load_html() # Check for lucide check icon assert 'data-lucide="check"' in html, "Should use lucide check icon" # Check that icon is created/shown after checking checkHabitFunc = html[html.find('async function checkHabit'):html.find('async function checkHabit') + 1500] assert 'lucide.createIcons()' in checkHabitFunc, \ "Should reinitialize lucide icons after adding check icon" print("✓ Check icon displays correctly") def main(): """Run all tests""" print("Running Story 10.0 Frontend Check Interaction Tests...\n") tests = [ test_checkbox_css_exists, test_checkbox_in_habit_card, test_checkbox_checked_state, test_check_habit_function_exists, test_check_habit_api_call, test_optimistic_ui_update, test_error_handling_revert, test_disabled_when_checked, test_streak_updates, test_check_icon_display, ] passed = 0 failed = 0 for test in tests: try: test() passed += 1 except AssertionError as e: print(f"✗ {test.__name__}: {e}") failed += 1 except Exception as e: print(f"✗ {test.__name__}: Unexpected error: {e}") failed += 1 print(f"\n{'='*50}") print(f"Results: {passed} passed, {failed} failed") print(f"{'='*50}") return 0 if failed == 0 else 1 if __name__ == '__main__': sys.exit(main())