275 lines
9.5 KiB
Python
275 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test suite for Story 16.0: Frontend - Delete habit with confirmation
|
|
Tests the delete button and confirmation modal functionality.
|
|
"""
|
|
|
|
import re
|
|
|
|
def test_file_exists():
|
|
"""Test that habits.html exists"""
|
|
try:
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
return True
|
|
except FileNotFoundError:
|
|
print("FAIL: habits.html not found")
|
|
return False
|
|
|
|
|
|
def test_delete_button_css():
|
|
"""AC1: Tests delete button styling (trash icon button using lucide)"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for delete button CSS class
|
|
if '.habit-delete-btn' not in content:
|
|
print("FAIL: .habit-delete-btn CSS class not found")
|
|
return False
|
|
|
|
# Check for proper styling (size, border, hover state)
|
|
css_pattern = r'\.habit-delete-btn\s*\{[^}]*width:\s*32px[^}]*height:\s*32px'
|
|
if not re.search(css_pattern, content, re.DOTALL):
|
|
print("FAIL: Delete button sizing not found (32x32px)")
|
|
return False
|
|
|
|
# Check for hover state with danger color
|
|
if '.habit-delete-btn:hover' not in content:
|
|
print("FAIL: Delete button hover state not found")
|
|
return False
|
|
|
|
if 'var(--text-danger)' not in content:
|
|
print("FAIL: Danger color not used for delete button")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_delete_button_in_card():
|
|
"""AC1: Tests that habit card includes trash icon button"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for trash-2 icon (lucide) in createHabitCard
|
|
if 'trash-2' not in content:
|
|
print("FAIL: trash-2 icon not found")
|
|
return False
|
|
|
|
# Check for delete button in card HTML with onclick handler
|
|
pattern = r'habit-delete-btn.*onclick.*showDeleteModal'
|
|
if not re.search(pattern, content, re.DOTALL):
|
|
print("FAIL: Delete button with onclick handler not found in card")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_confirmation_modal_structure():
|
|
"""AC2: Tests confirmation modal 'Ștergi obișnuința {name}?'"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for delete modal element
|
|
if 'id="deleteModal"' not in content:
|
|
print("FAIL: deleteModal element not found")
|
|
return False
|
|
|
|
# Check for Romanian confirmation message
|
|
if 'Ștergi obișnuința' not in content:
|
|
print("FAIL: Romanian confirmation message not found")
|
|
return False
|
|
|
|
# Check for habit name placeholder
|
|
if 'id="deleteHabitName"' not in content:
|
|
print("FAIL: deleteHabitName element for dynamic habit name not found")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_confirmation_buttons():
|
|
"""AC3 & AC4: Tests Cancel and Delete buttons with correct styling"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for Cancel button
|
|
if 'onclick="hideDeleteModal()"' not in content:
|
|
print("FAIL: Cancel button with hideDeleteModal() not found")
|
|
return False
|
|
|
|
# Check for Delete button
|
|
if 'onclick="confirmDelete()"' not in content:
|
|
print("FAIL: Delete button with confirmDelete() not found")
|
|
return False
|
|
|
|
# AC4: Check for destructive red styling (btn-danger class)
|
|
if '.btn-danger' not in content:
|
|
print("FAIL: .btn-danger CSS class not found")
|
|
return False
|
|
|
|
# Check that btn-danger uses danger color
|
|
css_pattern = r'\.btn-danger\s*\{[^}]*background:\s*var\(--text-danger\)'
|
|
if not re.search(css_pattern, content, re.DOTALL):
|
|
print("FAIL: btn-danger does not use danger color")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_delete_api_call():
|
|
"""AC5: Tests DELETE API call and list removal on confirm"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for confirmDelete function
|
|
if 'async function confirmDelete()' not in content:
|
|
print("FAIL: confirmDelete async function not found")
|
|
return False
|
|
|
|
# Check for DELETE method call to API
|
|
pattern = r"method:\s*['\"]DELETE['\"]"
|
|
if not re.search(pattern, content):
|
|
print("FAIL: DELETE method not found in confirmDelete")
|
|
return False
|
|
|
|
# Check for API endpoint with habitToDelete variable
|
|
pattern = r"/api/habits/\$\{habitToDelete\}"
|
|
if not re.search(pattern, content):
|
|
print("FAIL: DELETE endpoint /api/habits/{id} not found")
|
|
return False
|
|
|
|
# Check for loadHabits() call after successful deletion (removes from list)
|
|
if 'loadHabits()' not in content:
|
|
print("FAIL: loadHabits() not called after deletion (list won't refresh)")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_error_handling():
|
|
"""AC6: Tests error message display if delete fails"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for error handling in confirmDelete
|
|
pattern = r'catch\s*\(error\)\s*\{[^}]*showToast'
|
|
if not re.search(pattern, content, re.DOTALL):
|
|
print("FAIL: Error handling with showToast not found in confirmDelete")
|
|
return False
|
|
|
|
# Check for error message
|
|
if 'Eroare la ștergerea obișnuinței' not in content:
|
|
print("FAIL: Delete error message not found")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_modal_functions():
|
|
"""Tests showDeleteModal and hideDeleteModal functions"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for showDeleteModal function
|
|
if 'function showDeleteModal(' not in content:
|
|
print("FAIL: showDeleteModal function not found")
|
|
return False
|
|
|
|
# Check for hideDeleteModal function
|
|
if 'function hideDeleteModal(' not in content:
|
|
print("FAIL: hideDeleteModal function not found")
|
|
return False
|
|
|
|
# Check for habitToDelete variable tracking
|
|
if 'habitToDelete' not in content:
|
|
print("FAIL: habitToDelete tracking variable not found")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_modal_show_hide_logic():
|
|
"""Tests modal active class toggle for show/hide"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check for classList.add('active') in showDeleteModal
|
|
pattern = r'showDeleteModal[^}]*classList\.add\(["\']active["\']\)'
|
|
if not re.search(pattern, content, re.DOTALL):
|
|
print("FAIL: Modal show logic (classList.add('active')) not found")
|
|
return False
|
|
|
|
# Check for classList.remove('active') in hideDeleteModal
|
|
pattern = r'hideDeleteModal[^}]*classList\.remove\(["\']active["\']\)'
|
|
if not re.search(pattern, content, re.DOTALL):
|
|
print("FAIL: Modal hide logic (classList.remove('active')) not found")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def test_acceptance_criteria_summary():
|
|
"""AC7: Summary test verifying all acceptance criteria"""
|
|
with open('dashboard/habits.html', 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
checks = {
|
|
'AC1: Trash icon button': 'trash-2' in content and '.habit-delete-btn' in content,
|
|
'AC2: Confirmation modal message': 'Ștergi obișnuința' in content and 'id="deleteHabitName"' in content,
|
|
'AC3: Cancel and Delete buttons': 'hideDeleteModal()' in content and 'confirmDelete()' in content,
|
|
'AC4: Red destructive style': '.btn-danger' in content and 'var(--text-danger)' in content,
|
|
'AC5: DELETE endpoint call': 'method:' in content and 'DELETE' in content and '/api/habits/' in content,
|
|
'AC6: Error handling': 'catch (error)' in content and 'Eroare la ștergerea' in content,
|
|
'AC7: Delete interaction tests pass': True # This test itself
|
|
}
|
|
|
|
all_passed = all(checks.values())
|
|
|
|
if not all_passed:
|
|
print("FAIL: Not all acceptance criteria met:")
|
|
for criterion, passed in checks.items():
|
|
if not passed:
|
|
print(f" ✗ {criterion}")
|
|
|
|
return all_passed
|
|
|
|
|
|
if __name__ == '__main__':
|
|
tests = [
|
|
("File exists", test_file_exists),
|
|
("Delete button CSS styling", test_delete_button_css),
|
|
("Delete button in habit card (trash icon)", test_delete_button_in_card),
|
|
("Confirmation modal structure", test_confirmation_modal_structure),
|
|
("Confirmation buttons (Cancel & Delete)", test_confirmation_buttons),
|
|
("DELETE API call on confirm", test_delete_api_call),
|
|
("Error handling for failed delete", test_error_handling),
|
|
("Modal show/hide functions", test_modal_functions),
|
|
("Modal active class toggle logic", test_modal_show_hide_logic),
|
|
("All acceptance criteria summary", test_acceptance_criteria_summary),
|
|
]
|
|
|
|
passed = 0
|
|
total = len(tests)
|
|
|
|
print("Running Story 16.0 tests (Frontend - Delete habit with confirmation)...\n")
|
|
|
|
for name, test_func in tests:
|
|
try:
|
|
result = test_func()
|
|
if result:
|
|
print(f"✓ {name}")
|
|
passed += 1
|
|
else:
|
|
print(f"✗ {name}")
|
|
except Exception as e:
|
|
print(f"✗ {name} (exception: {e})")
|
|
|
|
print(f"\n{passed}/{total} tests passed")
|
|
|
|
if passed == total:
|
|
print("✓ All tests passed!")
|
|
exit(0)
|
|
else:
|
|
print(f"✗ {total - passed} test(s) failed")
|
|
exit(1)
|