Add autocomplete functionality for parks: implement BaseAutocomplete class and integrate with forms

This commit is contained in:
pacnpal
2025-02-22 13:36:24 -05:00
parent 5278ad39d0
commit 4339c5c5e0
15 changed files with 633 additions and 130 deletions

81
parks/tests/README.md Normal file
View File

@@ -0,0 +1,81 @@
# Park Search Tests
## Overview
Test suite for the park search functionality including:
- Autocomplete widget integration
- Search form validation
- Filter integration
- HTMX interaction
- View mode persistence
## Running Tests
```bash
# Run all park tests
uv run pytest parks/tests/
# Run specific search tests
uv run pytest parks/tests/test_search.py
# Run with coverage
uv run pytest --cov=parks parks/tests/
```
## Test Coverage
### Unit Tests
- `test_autocomplete_results`: Validates search result filtering
- `test_search_form_valid`: Ensures form validation works
- `test_autocomplete_class`: Checks autocomplete configuration
- `test_search_with_filters`: Verifies filter integration
### Integration Tests
- `test_empty_search`: Tests default behavior
- `test_partial_match_search`: Validates partial text matching
- `test_htmx_request_handling`: Ensures HTMX compatibility
- `test_view_mode_persistence`: Checks view state management
- `test_unauthenticated_access`: Verifies authentication requirements
### Security Tests
Parks search implements a tiered access approach:
- Basic search is public
- Autocomplete requires authentication
- Configuration set in settings.py: `AUTOCOMPLETE_BLOCK_UNAUTHENTICATED = True`
## Configuration
Tests use pytest-django and require:
- PostgreSQL database
- HTMX middleware
- Autocomplete app configuration
## Fixtures
The test suite uses standard Django test fixtures. No additional fixtures required.
## Common Issues
1. Database Errors
- Ensure PostGIS extensions are installed
- Verify database permissions
2. HTMX Tests
- Use `HTTP_HX_REQUEST` header for HTMX requests
- Check response content for HTMX attributes
## Adding New Tests
When adding tests, ensure:
1. Database isolation using `@pytest.mark.django_db`
2. Proper test naming following `test_*` convention
3. Clear test descriptions in docstrings
4. Coverage for both success and failure cases
5. HTMX interaction testing where applicable
## Future Improvements
- Add performance benchmarks
- Include accessibility tests
- Add Playwright e2e tests
- Implement geographic search tests

119
parks/tests/test_search.py Normal file
View File

@@ -0,0 +1,119 @@
import pytest
from django.urls import reverse
from django.test import Client
from parks.models import Park
from parks.forms import ParkAutocomplete, ParkSearchForm
@pytest.mark.django_db
class TestParkSearch:
def test_autocomplete_results(self, client: Client):
"""Test that autocomplete returns correct results"""
# Create test parks
park1 = Park.objects.create(name="Test Park")
park2 = Park.objects.create(name="Another Park")
park3 = Park.objects.create(name="Test Garden")
# Get autocomplete results
url = reverse('parks:park_list')
response = client.get(url, {'park': 'Test'})
# Check response
assert response.status_code == 200
content = response.content.decode()
assert park1.name in content
assert park3.name in content
assert park2.name not in content
def test_search_form_valid(self):
"""Test ParkSearchForm validation"""
form = ParkSearchForm(data={'park': ''})
assert form.is_valid()
def test_autocomplete_class(self):
"""Test ParkAutocomplete configuration"""
ac = ParkAutocomplete()
assert ac.model == Park
assert 'name' in ac.search_attrs
def test_search_with_filters(self, client: Client):
"""Test search works with filters"""
park = Park.objects.create(name="Test Park", status="OPERATING")
# Search with status filter
url = reverse('parks:park_list')
response = client.get(url, {
'park': str(park.pk),
'status': 'OPERATING'
})
assert response.status_code == 200
assert park.name in response.content.decode()
def test_empty_search(self, client: Client):
"""Test empty search returns all parks"""
Park.objects.create(name="Test Park")
Park.objects.create(name="Another Park")
url = reverse('parks:park_list')
response = client.get(url)
assert response.status_code == 200
content = response.content.decode()
assert "Test Park" in content
assert "Another Park" in content
def test_partial_match_search(self, client: Client):
"""Test partial matching in search"""
Park.objects.create(name="Adventure World")
Park.objects.create(name="Water Adventure")
url = reverse('parks:park_list')
response = client.get(url, {'park': 'Adv'})
assert response.status_code == 200
content = response.content.decode()
assert "Adventure World" in content
assert "Water Adventure" in content
def test_htmx_request_handling(self, client: Client):
"""Test HTMX-specific request handling"""
Park.objects.create(name="Test Park")
url = reverse('parks:park_list')
response = client.get(
url,
{'park': 'Test'},
HTTP_HX_REQUEST='true'
)
assert response.status_code == 200
assert "Test Park" in response.content.decode()
def test_view_mode_persistence(self, client: Client):
"""Test view mode is maintained during search"""
Park.objects.create(name="Test Park")
url = reverse('parks:park_list')
response = client.get(url, {
'park': 'Test',
'view_mode': 'list'
})
assert response.status_code == 200
assert 'data-view-mode="list"' in response.content.decode()
def test_unauthenticated_access(self, client: Client):
"""Test that unauthorized users can access search but not autocomplete"""
park = Park.objects.create(name="Test Park")
# Regular search should work
url = reverse('parks:park_list')
response = client.get(url, {'park_name': 'Test'})
assert response.status_code == 200
assert "Test Park" in response.content.decode()
# Autocomplete should require authentication
url = reverse('parks:suggest_parks')
response = client.get(url, {'search': 'Test'})
assert response.status_code == 302 # Redirects to login