#!/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)