diff --git a/dashboard/habits.html b/dashboard/habits.html index 65e93ef..92e736d 100644 --- a/dashboard/habits.html +++ b/dashboard/habits.html @@ -419,7 +419,7 @@ Files - + Habits diff --git a/dashboard/test_habits_navigation.py b/dashboard/test_habits_navigation.py new file mode 100644 index 0000000..9fad70e --- /dev/null +++ b/dashboard/test_habits_navigation.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +Test Suite for Story 13.0: Frontend - Add to dashboard navigation +Tests that Habit Tracker link is added to main navigation properly. +""" + +import os +import re + + +def test_file_existence(): + """Test that both index.html and habits.html exist.""" + assert os.path.exists('dashboard/index.html'), "index.html should exist" + assert os.path.exists('dashboard/habits.html'), "habits.html should exist" + print("✓ Both HTML files exist") + + +def test_index_habits_link(): + """Test that index.html includes Habits link pointing to /echo/habits.html.""" + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + content = f.read() + + # Check for Habits link with correct href + assert 'href="/echo/habits.html"' in content, "index.html should have link to /echo/habits.html" + + # Check that Habits link exists in navigation + habits_link_pattern = r']*href="/echo/habits\.html"[^>]*class="nav-item"[^>]*>.*?Habits' + assert re.search(habits_link_pattern, content, re.DOTALL), "Habits link should be in nav-item format" + + print("✓ index.html includes Habits link to /echo/habits.html (AC1, AC2)") + + +def test_index_flame_icon(): + """Test that index.html Habits link uses flame icon (lucide).""" + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + content = f.read() + + # Find the Habits nav item + habits_section = re.search( + r']*href="/echo/habits\.html"[^>]*>.*?', + content, + re.DOTALL + ) + + assert habits_section, "Habits link should exist" + habits_html = habits_section.group(0) + + # Check for flame icon (lucide) + assert 'data-lucide="flame"' in habits_html, "Habits link should use lucide flame icon" + + print("✓ index.html Habits link uses flame icon (AC3)") + + +def test_habits_back_to_dashboard(): + """Test that habits.html navigation includes link back to dashboard.""" + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + content = f.read() + + # Check for Dashboard link + assert 'href="/echo/index.html"' in content, "habits.html should link back to dashboard" + + # Check that Dashboard link exists in navigation + dashboard_link_pattern = r']*href="/echo/index\.html"[^>]*class="nav-item"[^>]*>.*?Dashboard' + assert re.search(dashboard_link_pattern, content, re.DOTALL), "Dashboard link should be in nav-item format" + + print("✓ habits.html includes link back to dashboard (AC4)") + + +def test_habits_flame_icon(): + """Test that habits.html Habits link also uses flame icon.""" + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + content = f.read() + + # Find the Habits nav item in habits.html + habits_section = re.search( + r']*href="/echo/habits\.html"[^>]*class="nav-item active"[^>]*>.*?', + content, + re.DOTALL + ) + + assert habits_section, "Habits link should exist in habits.html with active class" + habits_html = habits_section.group(0) + + # Check for flame icon (lucide) + assert 'data-lucide="flame"' in habits_html, "habits.html Habits link should use lucide flame icon" + + print("✓ habits.html Habits link uses flame icon (AC3)") + + +def test_active_state_styling(): + """Test that active state styling matches other nav items.""" + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + habits_content = f.read() + + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + index_content = f.read() + + # Check that habits.html has 'active' class on Habits nav item + habits_active = re.search( + r']*href="/echo/habits\.html"[^>]*class="nav-item active"', + habits_content + ) + assert habits_active, "Habits nav item should have 'active' class in habits.html" + + # Check that index.html has 'active' class on Dashboard nav item (pattern to follow) + index_active = re.search( + r']*href="/echo/index\.html"[^>]*class="nav-item active"', + index_content + ) + assert index_active, "Dashboard nav item should have 'active' class in index.html" + + # Both should use the same pattern (nav-item active) + print("✓ Active state styling matches other nav items (AC5)") + + +def test_mobile_navigation(): + """Test that mobile navigation is supported (shared nav structure).""" + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + index_content = f.read() + + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + habits_content = f.read() + + # Check that both files include swipe-nav.js for mobile navigation + assert 'swipe-nav.js' in index_content, "index.html should include swipe-nav.js for mobile navigation" + assert 'swipe-nav.js' in habits_content, "habits.html should include swipe-nav.js for mobile navigation" + + # Check that navigation uses the same class structure (nav-item) + # This ensures mobile navigation will work consistently + index_nav_items = len(re.findall(r'class="nav-item', index_content)) + habits_nav_items = len(re.findall(r'class="nav-item', habits_content)) + + assert index_nav_items >= 5, "index.html should have at least 5 nav items (including Habits)" + assert habits_nav_items >= 5, "habits.html should have at least 5 nav items" + + print("✓ Mobile navigation is supported (AC6)") + + +def test_navigation_completeness(): + """Test that navigation is complete on both pages.""" + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + index_content = f.read() + + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + habits_content = f.read() + + # Define expected navigation items + nav_items = [ + ('Dashboard', '/echo/index.html', 'layout-dashboard'), + ('Workspace', '/echo/workspace.html', 'code'), + ('KB', '/echo/notes.html', 'file-text'), + ('Files', '/echo/files.html', 'folder'), + ('Habits', '/echo/habits.html', 'flame') + ] + + # Check all items exist in both files + for label, href, icon in nav_items: + assert href in index_content, f"index.html should have link to {href}" + assert href in habits_content, f"habits.html should have link to {href}" + + # Check flame icon specifically + assert 'data-lucide="flame"' in index_content, "index.html should have flame icon" + assert 'data-lucide="flame"' in habits_content, "habits.html should have flame icon" + + print("✓ Navigation is complete on both pages with all 5 items") + + +def test_all_acceptance_criteria(): + """Summary test: verify all 7 acceptance criteria are met.""" + print("\n=== Testing All Acceptance Criteria ===") + + with open('dashboard/index.html', 'r', encoding='utf-8') as f: + index_content = f.read() + + with open('dashboard/habits.html', 'r', encoding='utf-8') as f: + habits_content = f.read() + + # AC1: index.html navigation includes 'Habits' link + ac1 = 'href="/echo/habits.html"' in index_content and 'class="nav-item"' in index_content + print(f"AC1 - index.html has Habits link: {'✓' if ac1 else '✗'}") + + # AC2: Link points to /echo/habits.html + ac2 = 'href="/echo/habits.html"' in index_content + print(f"AC2 - Link points to /echo/habits.html: {'✓' if ac2 else '✗'}") + + # AC3: Uses flame icon (lucide) + ac3 = 'data-lucide="flame"' in index_content and 'data-lucide="flame"' in habits_content + print(f"AC3 - Uses flame icon: {'✓' if ac3 else '✗'}") + + # AC4: habits.html navigation includes link back to dashboard + ac4 = 'href="/echo/index.html"' in habits_content + print(f"AC4 - habits.html links back to dashboard: {'✓' if ac4 else '✗'}") + + # AC5: Active state styling matches + ac5_habits = bool(re.search(r'href="/echo/habits\.html"[^>]*class="nav-item active"', habits_content)) + ac5_index = bool(re.search(r'href="/echo/index\.html"[^>]*class="nav-item active"', index_content)) + ac5 = ac5_habits and ac5_index + print(f"AC5 - Active state styling matches: {'✓' if ac5 else '✗'}") + + # AC6: Mobile navigation supported + ac6 = 'swipe-nav.js' in index_content and 'swipe-nav.js' in habits_content + print(f"AC6 - Mobile navigation supported: {'✓' if ac6 else '✗'}") + + # AC7: Tests pass (this test itself) + ac7 = True + print(f"AC7 - Tests for navigation pass: {'✓' if ac7 else '✗'}") + + assert all([ac1, ac2, ac3, ac4, ac5, ac6, ac7]), "All acceptance criteria should pass" + print("\n✓ All 7 acceptance criteria met!") + + +if __name__ == '__main__': + print("Running Story 13.0 Navigation Tests...\n") + + try: + test_file_existence() + test_index_habits_link() + test_index_flame_icon() + test_habits_back_to_dashboard() + test_habits_flame_icon() + test_active_state_styling() + test_mobile_navigation() + test_navigation_completeness() + test_all_acceptance_criteria() + + print("\n" + "="*50) + print("✓ ALL TESTS PASSED") + print("="*50) + + except AssertionError as e: + print(f"\n✗ TEST FAILED: {e}") + exit(1) + except Exception as e: + print(f"\n✗ ERROR: {e}") + exit(1)