#!/usr/bin/env python3 """ Test suite for Story 14.0: Frontend - Responsive mobile design Tests mobile responsiveness for habit tracker """ import re from pathlib import Path def test_file_exists(): """AC: Test file exists""" path = Path(__file__).parent / 'habits.html' assert path.exists(), "habits.html should exist" print("✓ File exists") def test_modal_fullscreen_mobile(): """AC1: Modal is full-screen on mobile (< 768px)""" path = Path(__file__).parent / 'habits.html' content = path.read_text() # Check for mobile media query assert '@media (max-width: 768px)' in content, "Should have mobile media query" # Find the mobile section by locating the media query and extracting content until the closing brace media_start = content.find('@media (max-width: 768px)') assert media_start != -1, "Should have mobile media query" # Extract a reasonable chunk after the media query (enough to include all mobile styles) mobile_chunk = content[media_start:media_start + 3000] # Check for modal full-screen styles within mobile section assert '.modal {' in mobile_chunk or '.modal{' in mobile_chunk, "Mobile section should include .modal styles" assert 'width: 100%' in mobile_chunk, "Modal should have 100% width on mobile" assert 'height: 100vh' in mobile_chunk, "Modal should have 100vh height on mobile" assert 'max-height: 100vh' in mobile_chunk, "Modal should have 100vh max-height on mobile" assert 'border-radius: 0' in mobile_chunk, "Modal should have no border-radius on mobile" print("✓ Modal is full-screen on mobile") def test_habit_cards_stack_vertically(): """AC2: Habit cards stack vertically on mobile""" path = Path(__file__).parent / 'habits.html' content = path.read_text() # Check for habits-list with flex-direction: column assert '.habits-list' in content, "Should have .habits-list class" # Extract habits-list styles habits_list_match = re.search(r'\.habits-list\s*\{([^}]+)\}', content) assert habits_list_match, "Should have .habits-list styles" habits_list_styles = habits_list_match.group(1) assert 'display: flex' in habits_list_styles or 'display:flex' in habits_list_styles, "habits-list should use flexbox" assert 'flex-direction: column' in habits_list_styles or 'flex-direction:column' in habits_list_styles, "habits-list should stack vertically" # Find the mobile section media_start = content.find('@media (max-width: 768px)') mobile_chunk = content[media_start:media_start + 3000] # Verify cards are full width on mobile assert '.habit-card {' in mobile_chunk or '.habit-card{' in mobile_chunk, "Should have .habit-card mobile styles" assert 'width: 100%' in mobile_chunk, "Should have 100% width on mobile" print("✓ Habit cards stack vertically on mobile") def test_touch_targets_44px(): """AC3: Touch targets >= 44x44px for checkbox""" path = Path(__file__).parent / 'habits.html' content = path.read_text() # Find the mobile section media_start = content.find('@media (max-width: 768px)') assert media_start != -1, "Should have mobile media query" mobile_chunk = content[media_start:media_start + 3000] # Check for checkbox sizing in mobile section assert '.habit-checkbox {' in mobile_chunk or '.habit-checkbox{' in mobile_chunk, "Should have .habit-checkbox styles in mobile section" # Extract width and height values from the mobile checkbox section checkbox_section_start = mobile_chunk.find('.habit-checkbox') checkbox_section = mobile_chunk[checkbox_section_start:checkbox_section_start + 300] width_match = re.search(r'width:\s*(\d+)px', checkbox_section) height_match = re.search(r'height:\s*(\d+)px', checkbox_section) assert width_match, "Checkbox should have width specified" assert height_match, "Checkbox should have height specified" width = int(width_match.group(1)) height = int(height_match.group(1)) # Verify touch target size (44x44px minimum for accessibility) assert width >= 44, f"Checkbox width should be >= 44px (got {width}px)" assert height >= 44, f"Checkbox height should be >= 44px (got {height}px)" # Check for other touch targets (buttons) assert 'min-height: 44px' in mobile_chunk, "Buttons should have min-height of 44px" print("✓ Touch targets are >= 44x44px") def test_mobile_optimized_keyboards(): """AC4: Form inputs use mobile-optimized keyboards""" path = Path(__file__).parent / 'habits.html' content = path.read_text() # Check for input field assert 'id="habitName"' in content, "Should have habitName input field" # Extract input element input_match = re.search(r']+id="habitName"[^>]*>', content) assert input_match, "Should have habitName input element" input_element = input_match.group(0) # Check for mobile-optimized attributes # autocapitalize="words" for proper names # autocomplete="off" to prevent autofill issues assert 'autocapitalize="words"' in input_element or 'autocapitalize=\'words\'' in input_element, \ "Input should have autocapitalize='words' for mobile optimization" assert 'autocomplete="off"' in input_element or 'autocomplete=\'off\'' in input_element, \ "Input should have autocomplete='off' to prevent autofill" # Verify type="text" is present (appropriate for habit names) assert 'type="text"' in input_element, "Input should have type='text'" print("✓ Form inputs use mobile-optimized keyboards") def test_swipe_navigation(): """AC5: Swipe navigation works (via swipe-nav.js)""" path = Path(__file__).parent / 'habits.html' content = path.read_text() # Check for swipe-nav.js inclusion assert 'swipe-nav.js' in content, "Should include swipe-nav.js for mobile swipe navigation" # Verify script tag assert '' in content, \ "Should have proper script tag for swipe-nav.js" # Check for viewport meta tag (required for proper mobile rendering) assert '= 44x44px checkbox_section_start = mobile_chunk.find('.habit-checkbox') checkbox_section = mobile_chunk[checkbox_section_start:checkbox_section_start + 300] assert 'width: 44px' in checkbox_section, "AC3: Touch targets 44px width" assert 'height: 44px' in checkbox_section, "AC3: Touch targets 44px height" # AC4: Mobile-optimized keyboards input_match = re.search(r']+id="habitName"[^>]*>', content) assert input_match and 'autocapitalize="words"' in input_match.group(0), "AC4: Mobile keyboards" # AC5: Swipe navigation assert 'swipe-nav.js' in content, "AC5: Swipe navigation" # AC6: Tests pass (this test itself) print("✓ All 6 acceptance criteria verified") def main(): """Run all tests""" tests = [ test_file_exists, test_modal_fullscreen_mobile, test_habit_cards_stack_vertically, test_touch_targets_44px, test_mobile_optimized_keyboards, test_swipe_navigation, test_mobile_button_sizing, test_responsive_layout_structure, test_all_acceptance_criteria ] print("Running Story 14.0 mobile responsiveness tests...\n") failed = [] for test in tests: try: test() except AssertionError as e: failed.append((test.__name__, str(e))) print(f"✗ {test.__name__}: {e}") print(f"\n{'='*60}") if failed: print(f"FAILED: {len(failed)} test(s) failed") for name, error in failed: print(f" - {name}: {error}") return False else: print(f"SUCCESS: All {len(tests)} tests passed! ✓") return True if __name__ == '__main__': import sys sys.exit(0 if main() else 1)