feat: US-011 - Frontend - Skip, lives display, and delete confirmation
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user