feat: 16.0 - Frontend - Delete habit with confirmation
This commit is contained in:
274
dashboard/test_habits_delete_ui.py
Normal file
274
dashboard/test_habits_delete_ui.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user