feat: US-011 - Frontend - Skip, lives display, and delete confirmation

This commit is contained in:
Echo
2026-02-10 17:07:25 +00:00
parent 5ed8680164
commit 8897de25ed
2 changed files with 262 additions and 5 deletions

View File

@@ -5,6 +5,7 @@ Story US-007: Frontend - Habit card component
Story US-008: Frontend - Create habit modal with all options
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
"""
import sys
@@ -996,6 +997,156 @@ def test_typecheck_us010():
print("✓ Test 55: Typecheck passes (all check-in functions and variables defined)")
# ========== US-011 Tests: Skip, lives display, and delete confirmation ==========
def test_skip_button_visible():
"""Test 56: Skip button visible next to lives hearts on each card"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check skip button CSS class exists
assert '.habit-card-skip-btn' in content, "Skip button CSS class should exist"
# Check skip button is in renderHabitCard
assert 'Skip day' in content, "Skip button text should be 'Skip day'"
assert 'onclick="skipHabitDay' in content, "Skip button should call skipHabitDay function"
# Check lives display structure has both hearts and button
assert 'habit-card-lives-hearts' in content, "Lives hearts should be wrapped in separate div"
print("✓ Test 56: Skip button visible next to lives hearts on each card")
def test_skip_confirmation_dialog():
"""Test 57: Clicking skip shows confirmation dialog"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check skipHabitDay function exists
assert 'async function skipHabitDay' in content, "skipHabitDay function should exist"
# Check confirmation dialog
assert 'Use 1 life to skip today?' in content, "Skip confirmation dialog should ask 'Use 1 life to skip today?'"
assert 'confirm(' in content, "Should use confirm dialog"
print("✓ Test 57: Clicking skip shows confirmation dialog")
def test_skip_sends_post_and_refreshes():
"""Test 58: Confirming skip sends POST /echo/api/habits/{id}/skip and refreshes card"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check API endpoint
assert "/echo/api/habits/${habitId}/skip" in content, "Should POST to /echo/api/habits/{id}/skip"
assert "method: 'POST'" in content, "Should use POST method"
# Check refresh
assert 'await loadHabits()' in content, "Should refresh habits list after skip"
print("✓ Test 58: Confirming skip sends POST /echo/api/habits/{id}/skip and refreshes card")
def test_skip_button_disabled_when_no_lives():
"""Test 59: Skip button disabled when lives is 0 with tooltip 'No lives left'"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check disabled condition
assert "habit.lives === 0 ? 'disabled' : ''" in content, "Skip button should be disabled when lives is 0"
# Check tooltip
assert 'No lives left' in content, "Tooltip should say 'No lives left' when disabled"
print("✓ Test 59: Skip button disabled when lives is 0 with tooltip 'No lives left'")
def test_skip_toast_message():
"""Test 60: Toast shows 'Day skipped. X lives remaining.' after skip"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check toast message format
assert 'Day skipped' in content, "Toast should include 'Day skipped'"
assert 'remaining' in content, "Toast should show remaining lives"
# Check life/lives plural handling
assert "remainingLives === 1 ? 'life' : 'lives'" in content, "Should handle singular/plural for lives"
print("✓ Test 60: Toast shows 'Day skipped. X lives remaining.' after skip")
def test_delete_confirmation_with_habit_name():
"""Test 61: Clicking trash icon shows delete confirmation dialog with habit name"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check delete function exists
assert 'async function deleteHabit' in content, "deleteHabit function should exist"
# Check confirmation uses habit name
assert 'Delete ${habitName}?' in content or 'Delete ' in content, "Delete confirmation should include habit name"
assert 'This cannot be undone' in content, "Delete confirmation should warn 'This cannot be undone'"
print("✓ Test 61: Clicking trash icon shows delete confirmation dialog with habit name")
def test_delete_sends_delete_request():
"""Test 62: Confirming delete sends DELETE /echo/api/habits/{id} and removes card"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check DELETE request
assert "method: 'DELETE'" in content, "Should use DELETE method"
assert "/echo/api/habits/${habitId}" in content or "/echo/api/habits/" in content, "Should DELETE to /echo/api/habits/{id}"
# Check refresh (removes card from DOM via re-render)
assert 'await loadHabits()' in content, "Should refresh habits list after delete"
print("✓ Test 62: Confirming delete sends DELETE /echo/api/habits/{id} and removes card")
def test_delete_toast_message():
"""Test 63: Toast shows 'Habit deleted' after deletion"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check success toast
assert 'Habit deleted' in content, "Toast should say 'Habit deleted'"
print("✓ Test 63: Toast shows 'Habit deleted' after deletion")
def test_skip_delete_no_console_errors():
"""Test 64: No obvious console error sources in skip and delete code"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check error handling in skipHabitDay
skip_func = content[content.find('async function skipHabitDay'):content.find('async function skipHabitDay') + 1500]
assert 'try' in skip_func and 'catch' in skip_func, "skipHabitDay should have try/catch"
assert 'console.error' in skip_func, "skipHabitDay should log errors"
# Check error handling in deleteHabit
delete_func = content[content.find('async function deleteHabit'):content.find('async function deleteHabit') + 1500]
assert 'try' in delete_func and 'catch' in delete_func, "deleteHabit should have try/catch"
assert 'console.error' in delete_func, "deleteHabit should log errors"
print("✓ Test 64: No obvious console error sources in skip and delete code")
def test_typecheck_us011():
"""Test 65: Typecheck passes (all skip and delete functions defined)"""
html_path = Path(__file__).parent.parent / 'habits.html'
content = html_path.read_text()
# Check function definitions
functions = [
'skipHabitDay',
'deleteHabit',
]
for func in functions:
assert f"function {func}" in content, \
f"{func} should be defined"
# Check that habits are cached for delete
assert 'localStorage.setItem' in content, "Should cache habits in localStorage"
assert 'habitsCache' in content, "Should use 'habitsCache' key"
print("✓ Test 65: Typecheck passes (all skip and delete functions defined)")
def run_all_tests():
"""Run all tests in sequence"""
tests = [
@@ -1059,9 +1210,20 @@ def run_all_tests():
test_checkin_prevent_context_menu,
test_checkin_event_listeners_attached,
test_typecheck_us010,
# US-011 tests
test_skip_button_visible,
test_skip_confirmation_dialog,
test_skip_sends_post_and_refreshes,
test_skip_button_disabled_when_no_lives,
test_skip_toast_message,
test_delete_confirmation_with_habit_name,
test_delete_sends_delete_request,
test_delete_toast_message,
test_skip_delete_no_console_errors,
test_typecheck_us011,
]
print(f"\nRunning {len(tests)} frontend tests for US-006, US-007, US-008, US-009, and US-010...\n")
print(f"\nRunning {len(tests)} frontend tests for US-006, US-007, US-008, US-009, US-010, and US-011...\n")
failed = []
for test in tests: