223 lines
7.7 KiB
Python
223 lines
7.7 KiB
Python
#!/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())
|