diff --git a/dashboard/habits.html b/dashboard/habits.html new file mode 100644 index 0000000..a25884b --- /dev/null +++ b/dashboard/habits.html @@ -0,0 +1,185 @@ + + + + + + + Echo · Habit Tracker + + + + + + +
+ + +
+ +
+ + + +
+ +
+ +

Nicio obișnuință încă. Creează prima!

+ +
+ + + +
+
+ + + + diff --git a/dashboard/habits.json b/dashboard/habits.json index e2e3ac1..0a4b66b 100644 --- a/dashboard/habits.json +++ b/dashboard/habits.json @@ -1,14 +1,4 @@ { - "lastUpdated": "2026-02-10T11:40:08.703720", - "habits": [ - { - "id": "habit-1770723608703", - "name": "Water Plants", - "frequency": "daily", - "createdAt": "2026-02-10T11:40:08.703082", - "completions": [ - "2026-02-10" - ] - } - ] + "lastUpdated": "2026-02-10T11:59:02.042Z", + "habits": [] } \ No newline at end of file diff --git a/dashboard/test_habits_html.py b/dashboard/test_habits_html.py new file mode 100644 index 0000000..e56deca --- /dev/null +++ b/dashboard/test_habits_html.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +""" +Test suite for habits.html page structure + +Tests: +1. File exists +2. Valid HTML5 structure +3. Uses common.css and swipe-nav.js +4. Has navigation bar matching dashboard style +5. Page title 'Habit Tracker' in header +6. Empty state message 'Nicio obișnuință încă. Creează prima!' +7. Add habit button with '+' icon (lucide) +""" + +import os +import re +from html.parser import HTMLParser + +# Path to habits.html +HABITS_HTML_PATH = 'dashboard/habits.html' + +class HTMLStructureParser(HTMLParser): + """Parser to extract specific elements from HTML""" + def __init__(self): + super().__init__() + self.title_text = None + self.css_files = [] + self.js_files = [] + self.nav_items = [] + self.page_title = None + self.empty_state_message = None + self.has_add_button = False + self.has_lucide_plus = False + self.in_title = False + self.in_page_title = False + self.in_empty_message = False + self.in_button = False + self.current_class = None + + def handle_starttag(self, tag, attrs): + attrs_dict = dict(attrs) + + # Track CSS and JS files + if tag == 'link' and attrs_dict.get('rel') == 'stylesheet': + self.css_files.append(attrs_dict.get('href', '')) + if tag == 'script' and 'src' in attrs_dict: + self.js_files.append(attrs_dict.get('src')) + + # Track title tag + if tag == 'title': + self.in_title = True + + # Track page title (h1 with class page-title) + if tag == 'h1' and 'page-title' in attrs_dict.get('class', ''): + self.in_page_title = True + + # Track nav items + if tag == 'a' and 'nav-item' in attrs_dict.get('class', ''): + href = attrs_dict.get('href', '') + classes = attrs_dict.get('class', '') + self.nav_items.append({'href': href, 'classes': classes}) + + # Track empty state message + if 'empty-state-message' in attrs_dict.get('class', ''): + self.in_empty_message = True + + # Track add habit button + if tag == 'button' and 'add-habit-btn' in attrs_dict.get('class', ''): + self.has_add_button = True + self.in_button = True + + # Track lucide plus icon in button context + if self.in_button and tag == 'i': + lucide_attr = attrs_dict.get('data-lucide', '') + if 'plus' in lucide_attr: + self.has_lucide_plus = True + + def handle_endtag(self, tag): + if tag == 'title': + self.in_title = False + if tag == 'h1': + self.in_page_title = False + if tag == 'p': + self.in_empty_message = False + if tag == 'button': + self.in_button = False + + def handle_data(self, data): + if self.in_title: + self.title_text = data.strip() + if self.in_page_title: + self.page_title = data.strip() + if self.in_empty_message: + self.empty_state_message = data.strip() + +def test_file_exists(): + """Test 1: File exists""" + assert os.path.exists(HABITS_HTML_PATH), f"File {HABITS_HTML_PATH} not found" + print("✓ Test 1: File exists") + +def test_valid_html5(): + """Test 2: Valid HTML5 structure""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + # Check DOCTYPE + assert content.strip().startswith(''), "Missing or incorrect DOCTYPE" + + # Check required tags + required_tags = ['', '', '', ''] + for tag in required_tags: + assert tag in content, f"Missing required tag: {tag}" + + # Check html lang attribute + assert 'lang="ro"' in content or "lang='ro'" in content, "Missing lang='ro' attribute on html tag" + + print("✓ Test 2: Valid HTML5 structure") + +def test_uses_common_css_and_swipe_nav(): + """Test 3: Uses common.css and swipe-nav.js""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + parser = HTMLStructureParser() + parser.feed(content) + + # Check for common.css + assert any('common.css' in css for css in parser.css_files), "Missing common.css" + + # Check for swipe-nav.js + assert any('swipe-nav.js' in js for js in parser.js_files), "Missing swipe-nav.js" + + print("✓ Test 3: Uses common.css and swipe-nav.js") + +def test_navigation_bar(): + """Test 4: Has navigation bar matching dashboard style""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + parser = HTMLStructureParser() + parser.feed(content) + + # Check that we have nav items + assert len(parser.nav_items) >= 4, f"Expected at least 4 nav items, found {len(parser.nav_items)}" + + # Check for Dashboard nav item + dashboard_items = [item for item in parser.nav_items if 'index.html' in item['href']] + assert len(dashboard_items) > 0, "Missing Dashboard nav item" + + # Check for habits nav item with active class + habits_items = [item for item in parser.nav_items if 'habits.html' in item['href']] + assert len(habits_items) > 0, "Missing Habits nav item" + assert any('active' in item['classes'] for item in habits_items), "Habits nav item should have 'active' class" + + # Check for header element with class 'header' + assert '
' in content, "Missing header element with class 'header'" + + print("✓ Test 4: Has navigation bar matching dashboard style") + +def test_page_title(): + """Test 5: Page title 'Habit Tracker' in header""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + parser = HTMLStructureParser() + parser.feed(content) + + # Check tag + assert parser.title_text is not None, "Missing <title> tag" + assert 'Habit Tracker' in parser.title_text, f"Expected 'Habit Tracker' in title, got: {parser.title_text}" + + # Check page header (h1) + assert parser.page_title is not None, "Missing page title (h1.page-title)" + assert 'Habit Tracker' in parser.page_title, f"Expected 'Habit Tracker' in page title, got: {parser.page_title}" + + print("✓ Test 5: Page title 'Habit Tracker' in header") + +def test_empty_state_message(): + """Test 6: Empty state message 'Nicio obișnuință încă. Creează prima!'""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + parser = HTMLStructureParser() + parser.feed(content) + + # Check empty state message + assert parser.empty_state_message is not None, "Missing empty state message" + expected_message = "Nicio obișnuință încă. Creează prima!" + assert parser.empty_state_message == expected_message, \ + f"Expected '{expected_message}', got: '{parser.empty_state_message}'" + + # Check for empty-state class + assert 'class="empty-state"' in content, "Missing empty-state element" + + print("✓ Test 6: Empty state message present") + +def test_add_habit_button(): + """Test 7: Add habit button with '+' icon (lucide)""" + with open(HABITS_HTML_PATH, 'r', encoding='utf-8') as f: + content = f.read() + + parser = HTMLStructureParser() + parser.feed(content) + + # Check for add habit button + assert parser.has_add_button, "Missing add habit button with class 'add-habit-btn'" + + # Check for lucide plus icon + assert parser.has_lucide_plus, "Missing lucide 'plus' icon in add habit button" + + # Check button text content + assert 'Adaugă obișnuință' in content, "Missing button text 'Adaugă obișnuință'" + + print("✓ Test 7: Add habit button with '+' icon (lucide)") + +def run_all_tests(): + """Run all tests""" + print("Running habits.html structure tests...\n") + + try: + test_file_exists() + test_valid_html5() + test_uses_common_css_and_swipe_nav() + test_navigation_bar() + test_page_title() + test_empty_state_message() + test_add_habit_button() + + print("\n✅ All tests passed!") + return True + except AssertionError as e: + print(f"\n❌ Test failed: {e}") + return False + except Exception as e: + print(f"\n❌ Unexpected error: {e}") + return False + +if __name__ == '__main__': + success = run_all_tests() + exit(0 if success else 1)