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