feat: 11.0 - Frontend - Create habit from form
This commit is contained in:
@@ -516,6 +516,7 @@
|
||||
// Create habit
|
||||
async function createHabit() {
|
||||
const nameInput = document.getElementById('habitName');
|
||||
const createBtn = document.getElementById('habitCreateBtn');
|
||||
const name = nameInput.value.trim();
|
||||
const frequency = document.querySelector('input[name="frequency"]:checked').value;
|
||||
|
||||
@@ -524,6 +525,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button during submission (loading state)
|
||||
createBtn.disabled = true;
|
||||
const originalText = createBtn.textContent;
|
||||
createBtn.textContent = 'Se creează...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/habits', {
|
||||
method: 'POST',
|
||||
@@ -532,16 +538,27 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Clear input field after successful creation
|
||||
nameInput.value = '';
|
||||
|
||||
hideHabitModal();
|
||||
showToast('Obișnuință creată cu succes!');
|
||||
loadHabits();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast('Eroare la crearea obișnuinței: ' + error);
|
||||
|
||||
// Re-enable button on error (modal stays open)
|
||||
createBtn.disabled = false;
|
||||
createBtn.textContent = originalText;
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Eroare la conectarea cu serverul');
|
||||
console.error(error);
|
||||
|
||||
// Re-enable button on error (modal stays open)
|
||||
createBtn.disabled = false;
|
||||
createBtn.textContent = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-10T11:59:02.042Z",
|
||||
"habits": []
|
||||
}
|
||||
"habits": [],
|
||||
"lastUpdated": "2026-02-10T12:39:00Z"
|
||||
}
|
||||
|
||||
209
dashboard/test_habits_form_submit.py
Normal file
209
dashboard/test_habits_form_submit.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for Story 11.0: Frontend - Create habit from form
|
||||
|
||||
Acceptance Criteria:
|
||||
1. Form submit calls POST /api/habits with name and frequency
|
||||
2. Shows loading state on submit (button disabled)
|
||||
3. On success: modal closes, list refreshes, new habit appears
|
||||
4. On error: shows error message in modal, modal stays open
|
||||
5. Input field cleared after successful creation
|
||||
6. Tests for form submission pass
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def test_form_submit_api_call():
|
||||
"""Test that createHabit calls POST /api/habits with correct data"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check that createHabit function exists
|
||||
assert 'async function createHabit()' in html, "createHabit function should exist"
|
||||
|
||||
# Check that it calls POST /api/habits
|
||||
assert "fetch('/api/habits'" in html, "Should fetch /api/habits endpoint"
|
||||
assert "method: 'POST'" in html, "Should use POST method"
|
||||
|
||||
# Check that it sends name and frequency in JSON body
|
||||
assert "body: JSON.stringify({ name, frequency })" in html, "Should send name and frequency in request body"
|
||||
|
||||
# Check that frequency is read from checked radio button
|
||||
assert "document.querySelector('input[name=\"frequency\"]:checked').value" in html, "Should read frequency from radio buttons"
|
||||
|
||||
print("✓ Form submission calls POST /api/habits with name and frequency")
|
||||
|
||||
def test_loading_state_on_submit():
|
||||
"""Test that button is disabled during submission"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check that createBtn is referenced
|
||||
assert "createBtn = document.getElementById('habitCreateBtn')" in html, "Should get create button element"
|
||||
|
||||
# Check that button is disabled before fetch
|
||||
assert "createBtn.disabled = true" in html, "Button should be disabled during submission"
|
||||
|
||||
# Check that button text changes to loading state
|
||||
assert "createBtn.textContent = 'Se creează...'" in html or "Se creează" in html, "Should show loading text during submission"
|
||||
|
||||
# Check that original text is stored for restoration
|
||||
assert "originalText = createBtn.textContent" in html, "Should store original button text"
|
||||
|
||||
print("✓ Shows loading state on submit (button disabled)")
|
||||
|
||||
def test_success_behavior():
|
||||
"""Test behavior on successful habit creation"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check for success handling block
|
||||
assert "if (response.ok)" in html, "Should check for successful response"
|
||||
|
||||
# Check that modal closes on success
|
||||
assert "hideHabitModal()" in html, "Modal should close on success"
|
||||
|
||||
# Check that habits list is refreshed
|
||||
assert "loadHabits()" in html, "Should reload habits list on success"
|
||||
|
||||
# Check that success toast is shown
|
||||
assert "showToast(" in html and "succes" in html, "Should show success toast"
|
||||
|
||||
print("✓ On success: modal closes, list refreshes, new habit appears")
|
||||
|
||||
def test_error_behavior():
|
||||
"""Test behavior on error (modal stays open, shows error)"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check for error handling in response
|
||||
assert "else {" in html or "!response.ok" in html, "Should handle error responses"
|
||||
|
||||
# Check for catch block for network errors
|
||||
assert "catch (error)" in html, "Should catch network errors"
|
||||
|
||||
# Check that error toast is shown
|
||||
assert "showToast('Eroare" in html, "Should show error toast on failure"
|
||||
|
||||
# Check that button is re-enabled on error (so modal stays usable)
|
||||
createHabit_block = html[html.find('async function createHabit()'):html.find('async function createHabit()') + 2000]
|
||||
|
||||
# Count occurrences of button re-enable in error paths
|
||||
error_section = html[html.find('} else {'):html.find('} catch (error)') + 500]
|
||||
assert 'createBtn.disabled = false' in error_section, "Button should be re-enabled on error"
|
||||
assert 'createBtn.textContent = originalText' in error_section, "Button text should be restored on error"
|
||||
|
||||
print("✓ On error: shows error message, modal stays open (button re-enabled)")
|
||||
|
||||
def test_input_cleared_after_success():
|
||||
"""Test that input field is cleared after successful creation"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Find the success block
|
||||
success_section = html[html.find('if (response.ok)'):html.find('if (response.ok)') + 500]
|
||||
|
||||
# Check that nameInput.value is cleared
|
||||
assert "nameInput.value = ''" in success_section or 'nameInput.value = ""' in success_section, \
|
||||
"Input field should be cleared after successful creation"
|
||||
|
||||
print("✓ Input field cleared after successful creation")
|
||||
|
||||
def test_form_validation_still_works():
|
||||
"""Test that existing form validation is still in place"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check that empty name validation exists
|
||||
assert "if (!name)" in html, "Should validate for empty name"
|
||||
assert "name = nameInput.value.trim()" in html, "Should trim name before validation"
|
||||
|
||||
# Check that create button is disabled when name is empty
|
||||
assert "nameInput.addEventListener('input'" in html, "Should listen to input changes"
|
||||
assert "createBtn.disabled = name.length === 0" in html, "Button should be disabled when name is empty"
|
||||
|
||||
print("✓ Form validation still works (empty name check)")
|
||||
|
||||
def test_modal_reset_on_open():
|
||||
"""Test that modal resets form when opened"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check showAddHabitModal function
|
||||
assert 'function showAddHabitModal()' in html, "showAddHabitModal function should exist"
|
||||
|
||||
# Check that form is reset when modal opens
|
||||
modal_function = html[html.find('function showAddHabitModal()'):html.find('function showAddHabitModal()') + 500]
|
||||
assert "nameInput.value = ''" in modal_function or 'nameInput.value = ""' in modal_function, \
|
||||
"Should clear name input when opening modal"
|
||||
|
||||
print("✓ Modal resets form when opened")
|
||||
|
||||
def test_enter_key_submission():
|
||||
"""Test that Enter key can submit the form"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
# Check for Enter key handler
|
||||
assert "addEventListener('keypress'" in html, "Should listen for keypress events"
|
||||
assert "e.key === 'Enter'" in html, "Should check for Enter key"
|
||||
assert "!createBtn.disabled" in html, "Should check if button is enabled before submitting"
|
||||
assert "createHabit()" in html, "Should call createHabit on Enter"
|
||||
|
||||
print("✓ Enter key submits form when button is enabled")
|
||||
|
||||
def test_all_acceptance_criteria():
|
||||
"""Summary test - verify all acceptance criteria are met"""
|
||||
with open('dashboard/habits.html', 'r') as f:
|
||||
html = f.read()
|
||||
|
||||
checks = [
|
||||
("'/api/habits'" in html and "method: 'POST'" in html and "body: JSON.stringify({ name, frequency })" in html,
|
||||
"1. Form submit calls POST /api/habits with name and frequency"),
|
||||
|
||||
("createBtn.disabled = true" in html and "Se creează" in html,
|
||||
"2. Shows loading state on submit (button disabled)"),
|
||||
|
||||
("hideHabitModal()" in html and "loadHabits()" in html and "response.ok" in html,
|
||||
"3. On success: modal closes, list refreshes, new habit appears"),
|
||||
|
||||
("catch (error)" in html and "createBtn.disabled = false" in html,
|
||||
"4. On error: shows error message, modal stays open"),
|
||||
|
||||
("nameInput.value = ''" in html or 'nameInput.value = ""' in html,
|
||||
"5. Input field cleared after successful creation"),
|
||||
|
||||
(True, "6. Tests for form submission pass (this test!)")
|
||||
]
|
||||
|
||||
all_pass = True
|
||||
for condition, description in checks:
|
||||
status = "✓" if condition else "✗"
|
||||
print(f" {status} {description}")
|
||||
if not condition:
|
||||
all_pass = False
|
||||
|
||||
assert all_pass, "Not all acceptance criteria are met"
|
||||
print("\n✓ All acceptance criteria verified!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
test_form_submit_api_call()
|
||||
test_loading_state_on_submit()
|
||||
test_success_behavior()
|
||||
test_error_behavior()
|
||||
test_input_cleared_after_success()
|
||||
test_form_validation_still_works()
|
||||
test_modal_reset_on_open()
|
||||
test_enter_key_submission()
|
||||
test_all_acceptance_criteria()
|
||||
|
||||
print("\n✅ All Story 11.0 tests passed!")
|
||||
sys.exit(0)
|
||||
except AssertionError as e:
|
||||
print(f"\n❌ Test failed: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ Unexpected error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user