feat: 16.0 - Frontend - Delete habit with confirmation
This commit is contained in:
@@ -153,6 +153,36 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Delete button */
|
||||||
|
.habit-delete-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-base);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.habit-delete-btn:hover {
|
||||||
|
border-color: var(--text-danger);
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.habit-delete-btn svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.habit-delete-btn:hover svg {
|
||||||
|
color: var(--text-danger);
|
||||||
|
}
|
||||||
|
|
||||||
/* Habit checkbox */
|
/* Habit checkbox */
|
||||||
.habit-checkbox {
|
.habit-checkbox {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
@@ -359,6 +389,58 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Delete confirmation modal */
|
||||||
|
.confirm-modal {
|
||||||
|
background: var(--bg-base);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-5);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-modal-title {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-modal-message {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--text-danger);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid var(--text-danger);
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
border-color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile responsiveness */
|
/* Mobile responsiveness */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.main {
|
.main {
|
||||||
@@ -522,6 +604,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation Modal -->
|
||||||
|
<div class="modal-overlay" id="deleteModal">
|
||||||
|
<div class="confirm-modal">
|
||||||
|
<h2 class="confirm-modal-title">Ștergi obișnuința?</h2>
|
||||||
|
<p class="confirm-modal-message" id="deleteModalMessage">
|
||||||
|
Ștergi obișnuința <strong id="deleteHabitName"></strong>?
|
||||||
|
</p>
|
||||||
|
<div class="confirm-modal-actions">
|
||||||
|
<button class="btn btn-secondary" onclick="hideDeleteModal()">Anulează</button>
|
||||||
|
<button class="btn btn-danger" id="confirmDeleteBtn" onclick="confirmDelete()">Șterge</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -731,6 +827,9 @@
|
|||||||
<span id="streak-${habit.id}">${habit.streak || 0}</span>
|
<span id="streak-${habit.id}">${habit.streak || 0}</span>
|
||||||
<span>🔥</span>
|
<span>🔥</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="habit-delete-btn" onclick="showDeleteModal('${habit.id}', '${escapeHtml(habit.name).replace(/'/g, "'")}')">
|
||||||
|
<i data-lucide="trash-2"></i>
|
||||||
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
@@ -807,6 +906,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete habit functions
|
||||||
|
let habitToDelete = null;
|
||||||
|
|
||||||
|
function showDeleteModal(habitId, habitName) {
|
||||||
|
habitToDelete = habitId;
|
||||||
|
const modal = document.getElementById('deleteModal');
|
||||||
|
const nameElement = document.getElementById('deleteHabitName');
|
||||||
|
|
||||||
|
// Decode HTML entities for display
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = habitName;
|
||||||
|
nameElement.textContent = tempDiv.textContent;
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDeleteModal() {
|
||||||
|
const modal = document.getElementById('deleteModal');
|
||||||
|
modal.classList.remove('active');
|
||||||
|
habitToDelete = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDelete() {
|
||||||
|
if (!habitToDelete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBtn = document.getElementById('confirmDeleteBtn');
|
||||||
|
|
||||||
|
// Disable button during deletion
|
||||||
|
deleteBtn.disabled = true;
|
||||||
|
const originalText = deleteBtn.textContent;
|
||||||
|
deleteBtn.textContent = 'Se șterge...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/habits/${habitToDelete}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete habit');
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDeleteModal();
|
||||||
|
showToast('Obișnuință ștearsă cu succes');
|
||||||
|
loadHabits();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting habit:', error);
|
||||||
|
showToast('Eroare la ștergerea obișnuinței. Încearcă din nou.');
|
||||||
|
|
||||||
|
// Re-enable button on error
|
||||||
|
deleteBtn.disabled = false;
|
||||||
|
deleteBtn.textContent = originalText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load habits on page load
|
// Load habits on page load
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadHabits();
|
loadHabits();
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
|
"lastUpdated": "2026-02-10T13:19:32.381583",
|
||||||
"habits": [
|
"habits": [
|
||||||
{
|
{
|
||||||
"id": "habit-test1",
|
"id": "habit-1770729572381",
|
||||||
"name": "Test Habit",
|
"name": "Water Plants",
|
||||||
"frequency": "daily",
|
"frequency": "daily",
|
||||||
"createdAt": "2026-02-01T10:00:00Z",
|
"createdAt": "2026-02-10T13:19:32.381005",
|
||||||
"completions": [
|
"completions": [
|
||||||
"2026-02-10"
|
"2026-02-10"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"lastUpdated": "2026-02-10T10:00:00Z"
|
|
||||||
}
|
}
|
||||||
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)
|
||||||
38
progress.txt
38
progress.txt
@@ -578,3 +578,41 @@ NEXT STEPS:
|
|||||||
Files modified:
|
Files modified:
|
||||||
- dashboard/habits.html (enhanced mobile CSS, added input attributes)
|
- dashboard/habits.html (enhanced mobile CSS, added input attributes)
|
||||||
- dashboard/test_habits_mobile.py (created)
|
- dashboard/test_habits_mobile.py (created)
|
||||||
|
|
||||||
|
[✓] Story 15.0: Backend - Delete habit endpoint
|
||||||
|
Commit: 0f9c0de
|
||||||
|
Date: 2026-02-10
|
||||||
|
|
||||||
|
Implementation:
|
||||||
|
- Added do_DELETE method to api.py for handling DELETE requests
|
||||||
|
- Route: DELETE /api/habits/{id} deletes habit by ID
|
||||||
|
- Extracts habit ID from URL path (/api/habits/{id})
|
||||||
|
- Returns 404 if habit not found in habits.json
|
||||||
|
- Removes habit from habits array using list.pop(index)
|
||||||
|
- Updates lastUpdated timestamp after deletion
|
||||||
|
- Returns 200 with success message, including deleted habit ID
|
||||||
|
- Graceful error handling for missing/corrupt habits.json (returns 404)
|
||||||
|
- Follows existing API patterns (similar to handle_habits_check)
|
||||||
|
- Error responses include descriptive error messages
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- Created dashboard/test_habits_delete.py with 6 comprehensive tests
|
||||||
|
- Tests for habit removal from habits.json file (AC1)
|
||||||
|
- Tests for 200 status with success message response (AC2)
|
||||||
|
- Tests for 404 when habit not found (AC3)
|
||||||
|
- Tests for lastUpdated timestamp update (AC4)
|
||||||
|
- Tests for edge cases: deleting already deleted habit, invalid paths
|
||||||
|
- Tests for graceful handling when habits.json is missing
|
||||||
|
- All 6 tests pass ✓ (AC5)
|
||||||
|
- All previous tests (schema, GET, POST, streak, check) still pass ✓
|
||||||
|
|
||||||
|
Files modified:
|
||||||
|
- dashboard/api.py (added do_DELETE method and handle_habits_delete)
|
||||||
|
- dashboard/test_habits_delete.py (created)
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
NEXT STEPS:
|
||||||
|
- Continue with remaining 2 stories (16.0, 17.0)
|
||||||
|
|
||||||
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|||||||
Reference in New Issue
Block a user