feat: 13.0 - Frontend - Add to dashboard navigation
This commit is contained in:
@@ -419,7 +419,7 @@
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/habits.html" class="nav-item active">
|
||||
<i data-lucide="target"></i>
|
||||
<i data-lucide="flame"></i>
|
||||
<span>Habits</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
|
||||
@@ -1075,6 +1075,10 @@
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<a href="/echo/habits.html" class="nav-item">
|
||||
<i data-lucide="flame"></i>
|
||||
<span>Habits</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
235
dashboard/test_habits_navigation.py
Normal file
235
dashboard/test_habits_navigation.py
Normal file
@@ -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'<a[^>]*href="/echo/habits\.html"[^>]*class="nav-item"[^>]*>.*?<span>Habits</span>'
|
||||
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'<a[^>]*href="/echo/habits\.html"[^>]*>.*?</a>',
|
||||
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'<a[^>]*href="/echo/index\.html"[^>]*class="nav-item"[^>]*>.*?<span>Dashboard</span>'
|
||||
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'<a[^>]*href="/echo/habits\.html"[^>]*class="nav-item active"[^>]*>.*?</a>',
|
||||
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'<a[^>]*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'<a[^>]*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)
|
||||
Reference in New Issue
Block a user