feat: US-012 - Frontend - Filter and sort controls
This commit is contained in:
@@ -6,6 +6,7 @@ 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
|
||||
Story US-012: Frontend - Filter and sort controls
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -1147,6 +1148,146 @@ def test_typecheck_us011():
|
||||
|
||||
print("✓ Test 65: Typecheck passes (all skip and delete functions defined)")
|
||||
|
||||
### US-012: Filter and sort controls ###
|
||||
|
||||
def test_filter_bar_exists():
|
||||
"""Test 66: Filter bar with category, status, and sort dropdowns appears above habit grid"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'class="filter-bar"' in content, "Should have filter-bar element"
|
||||
assert 'id="categoryFilter"' in content, "Should have category filter dropdown"
|
||||
assert 'id="statusFilter"' in content, "Should have status filter dropdown"
|
||||
assert 'id="sortSelect"' in content, "Should have sort dropdown"
|
||||
print("✓ Test 66: Filter bar with dropdowns exists")
|
||||
|
||||
def test_category_filter_options():
|
||||
"""Test 67: Category filter has All, Work, Health, Growth, Personal options"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check for category options
|
||||
assert 'value="all">All</option>' in content, "Should have 'All' option"
|
||||
assert 'value="work">Work</option>' in content, "Should have 'Work' option"
|
||||
assert 'value="health">Health</option>' in content, "Should have 'Health' option"
|
||||
assert 'value="growth">Growth</option>' in content, "Should have 'Growth' option"
|
||||
assert 'value="personal">Personal</option>' in content, "Should have 'Personal' option"
|
||||
print("✓ Test 67: Category filter has correct options")
|
||||
|
||||
def test_status_filter_options():
|
||||
"""Test 68: Status filter has All, Active Today, Done Today, Overdue options"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'value="all">All</option>' in content, "Should have 'All' option"
|
||||
assert 'value="active_today">Active Today</option>' in content, "Should have 'Active Today' option"
|
||||
assert 'value="done_today">Done Today</option>' in content, "Should have 'Done Today' option"
|
||||
assert 'value="overdue">Overdue</option>' in content, "Should have 'Overdue' option"
|
||||
print("✓ Test 68: Status filter has correct options")
|
||||
|
||||
def test_sort_dropdown_options():
|
||||
"""Test 69: Sort dropdown has Priority, Name, and Streak options (asc/desc)"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'value="priority_asc"' in content, "Should have 'Priority (Low to High)' option"
|
||||
assert 'value="priority_desc"' in content, "Should have 'Priority (High to Low)' option"
|
||||
assert 'value="name_asc"' in content, "Should have 'Name A-Z' option"
|
||||
assert 'value="name_desc"' in content, "Should have 'Name Z-A' option"
|
||||
assert 'value="streak_desc"' in content, "Should have 'Streak (Highest)' option"
|
||||
assert 'value="streak_asc"' in content, "Should have 'Streak (Lowest)' option"
|
||||
print("✓ Test 69: Sort dropdown has correct options")
|
||||
|
||||
def test_filter_functions_exist():
|
||||
"""Test 70: applyFiltersAndSort, filterHabits, and sortHabits functions are defined"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'function applyFiltersAndSort()' in content, "Should have applyFiltersAndSort function"
|
||||
assert 'function filterHabits(' in content, "Should have filterHabits function"
|
||||
assert 'function sortHabits(' in content, "Should have sortHabits function"
|
||||
assert 'function restoreFilters()' in content, "Should have restoreFilters function"
|
||||
print("✓ Test 70: Filter and sort functions are defined")
|
||||
|
||||
def test_filter_calls_on_change():
|
||||
"""Test 71: Filter dropdowns call applyFiltersAndSort on change"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert 'onchange="applyFiltersAndSort()"' in content, "Filters should call applyFiltersAndSort on change"
|
||||
|
||||
# Count how many times onchange appears (should be 3: category, status, sort)
|
||||
count = content.count('onchange="applyFiltersAndSort()"')
|
||||
assert count >= 3, f"Should have at least 3 onchange handlers, found {count}"
|
||||
print("✓ Test 71: Filter dropdowns call applyFiltersAndSort on change")
|
||||
|
||||
def test_localstorage_persistence():
|
||||
"""Test 72: Filter selections are saved to localStorage"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert "localStorage.setItem('habitCategoryFilter'" in content, "Should save category filter to localStorage"
|
||||
assert "localStorage.setItem('habitStatusFilter'" in content, "Should save status filter to localStorage"
|
||||
assert "localStorage.setItem('habitSort'" in content, "Should save sort to localStorage"
|
||||
print("✓ Test 72: Filter selections saved to localStorage")
|
||||
|
||||
def test_localstorage_restore():
|
||||
"""Test 73: Filter selections are restored from localStorage on page load"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
assert "localStorage.getItem('habitCategoryFilter')" in content, "Should restore category filter from localStorage"
|
||||
assert "localStorage.getItem('habitStatusFilter')" in content, "Should restore status filter from localStorage"
|
||||
assert "localStorage.getItem('habitSort')" in content, "Should restore sort from localStorage"
|
||||
assert 'restoreFilters()' in content, "Should call restoreFilters on page load"
|
||||
print("✓ Test 73: Filter selections restored from localStorage")
|
||||
|
||||
def test_filter_logic_implementation():
|
||||
"""Test 74: filterHabits function checks category and status correctly"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check category filter logic
|
||||
assert "categoryFilter !== 'all'" in content, "Should check if category filter is not 'all'"
|
||||
assert "habit.category" in content, "Should compare habit.category"
|
||||
|
||||
# Check status filter logic
|
||||
assert "statusFilter !== 'all'" in content, "Should check if status filter is not 'all'"
|
||||
assert "should_check_today" in content or "shouldCheckToday" in content, "Should use should_check_today for status filtering"
|
||||
print("✓ Test 74: Filter logic checks category and status")
|
||||
|
||||
def test_sort_logic_implementation():
|
||||
"""Test 75: sortHabits function handles all sort options"""
|
||||
habits_path = Path(__file__).parent.parent / 'habits.html'
|
||||
content = habits_path.read_text()
|
||||
|
||||
# Check that sort function handles all options
|
||||
assert "'priority_asc'" in content, "Should handle priority_asc"
|
||||
assert "'priority_desc'" in content, "Should handle priority_desc"
|
||||
assert "'name_asc'" in content, "Should handle name_asc"
|
||||
assert "'name_desc'" in content, "Should handle name_desc"
|
||||
assert "'streak_desc'" in content, "Should handle streak_desc"
|
||||
assert "'streak_asc'" in content, "Should handle streak_asc"
|
||||
assert 'localeCompare' in content, "Should use localeCompare for name sorting"
|
||||
print("✓ Test 75: Sort logic handles all options")
|
||||
|
||||
def test_backend_provides_should_check_today():
|
||||
"""Test 76: Backend API enriches habits with should_check_today field"""
|
||||
api_path = Path(__file__).parent.parent / 'api.py'
|
||||
content = api_path.read_text()
|
||||
|
||||
# Check that should_check_today is added in handle_habits_get
|
||||
assert "should_check_today" in content, "Backend should add should_check_today field"
|
||||
assert "habits_helpers.should_check_today" in content, "Should use should_check_today helper"
|
||||
print("✓ Test 76: Backend provides should_check_today field")
|
||||
|
||||
def test_typecheck_us012():
|
||||
"""Test 77: Typecheck passes for api.py"""
|
||||
api_path = Path(__file__).parent.parent / 'api.py'
|
||||
result = os.system(f'python3 -m py_compile {api_path} 2>/dev/null')
|
||||
assert result == 0, "api.py should pass typecheck (syntax check)"
|
||||
print("✓ Test 77: Typecheck passes")
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all tests in sequence"""
|
||||
tests = [
|
||||
@@ -1221,9 +1362,22 @@ def run_all_tests():
|
||||
test_delete_toast_message,
|
||||
test_skip_delete_no_console_errors,
|
||||
test_typecheck_us011,
|
||||
# US-012 tests
|
||||
test_filter_bar_exists,
|
||||
test_category_filter_options,
|
||||
test_status_filter_options,
|
||||
test_sort_dropdown_options,
|
||||
test_filter_functions_exist,
|
||||
test_filter_calls_on_change,
|
||||
test_localstorage_persistence,
|
||||
test_localstorage_restore,
|
||||
test_filter_logic_implementation,
|
||||
test_sort_logic_implementation,
|
||||
test_backend_provides_should_check_today,
|
||||
test_typecheck_us012,
|
||||
]
|
||||
|
||||
print(f"\nRunning {len(tests)} frontend tests for US-006, US-007, US-008, US-009, US-010, and US-011...\n")
|
||||
print(f"\nRunning {len(tests)} frontend tests for US-006, US-007, US-008, US-009, US-010, US-011, and US-012...\n")
|
||||
|
||||
failed = []
|
||||
for test in tests:
|
||||
|
||||
Reference in New Issue
Block a user