""" Tests for template rendering and structure Tests cover: - Template inheritance - Microformats2 markup - HTML structure and validity - Template variables and context - Flash message rendering - Error templates """ import pytest from starpunk import create_app from starpunk.notes import create_note @pytest.fixture def app(tmp_path): """Create test application""" test_data_dir = tmp_path / "data" test_data_dir.mkdir(parents=True, exist_ok=True) test_config = { "TESTING": True, "DATABASE_PATH": test_data_dir / "starpunk.db", "DATA_PATH": test_data_dir, "NOTES_PATH": test_data_dir / "notes", "SESSION_SECRET": "test-secret", "SITE_URL": "http://localhost:5000", "ADMIN_ME": "https://test.example.com", "DEV_MODE": False, "SITE_NAME": "Test StarPunk", "VERSION": "0.5.0", } app = create_app(config=test_config) yield app @pytest.fixture def client(app): """Test client""" return app.test_client() class TestBaseTemplate: """Test base.html template""" def test_base_has_doctype(self, client): """Test base template has HTML5 doctype""" response = client.get("/") assert response.status_code == 200 assert response.data.startswith(b"") def test_base_has_charset(self, client): """Test base template has charset meta tag""" response = client.get("/") assert response.status_code == 200 assert b'charset="UTF-8"' in response.data or b"charset=UTF-8" in response.data def test_base_has_viewport(self, client): """Test base template has viewport meta tag""" response = client.get("/") assert response.status_code == 200 assert b"viewport" in response.data assert b"width=device-width" in response.data def test_base_has_title(self, client): """Test base template has title""" response = client.get("/") assert response.status_code == 200 assert b"" in response.data assert b"StarPunk" in response.data def test_base_has_stylesheet(self, client): """Test base template links to stylesheet""" response = client.get("/") assert response.status_code == 200 assert b"<link" in response.data assert b"stylesheet" in response.data assert b"style.css" in response.data def test_base_has_rss_link(self, client): """Test base template has RSS feed link""" response = client.get("/") assert response.status_code == 200 assert b"application/rss+xml" in response.data or b"feed.xml" in response.data def test_base_has_header(self, client): """Test base template has header""" response = client.get("/") assert response.status_code == 200 assert b"<header" in response.data def test_base_has_main(self, client): """Test base template has main content area""" response = client.get("/") assert response.status_code == 200 assert b"<main" in response.data def test_base_has_footer(self, client): """Test base template has footer""" response = client.get("/") assert response.status_code == 200 assert b"<footer" in response.data def test_base_footer_has_version(self, client): """Test footer shows version number""" response = client.get("/") assert response.status_code == 200 assert b"0.5.0" in response.data def test_base_has_navigation(self, client): """Test base has navigation""" response = client.get("/") assert response.status_code == 200 assert b"<nav" in response.data or b'href="/"' in response.data class TestHomepageTemplate: """Test index.html template""" def test_homepage_has_h_feed(self, client): """Test homepage has h-feed microformat""" with client.application.test_request_context(): create_note("# Test\n\nContent", published=True) response = client.get("/") assert response.status_code == 200 assert b"h-feed" in response.data def test_homepage_notes_have_h_entry(self, client): """Test notes on homepage have h-entry""" with client.application.test_request_context(): create_note("# Test\n\nContent", published=True) response = client.get("/") assert response.status_code == 200 assert b"h-entry" in response.data def test_homepage_empty_state(self, client): """Test homepage shows message when no notes""" response = client.get("/") assert response.status_code == 200 # Should have some indication of empty state data_lower = response.data.lower() assert ( b"no notes" in data_lower or b"welcome" in data_lower or b"get started" in data_lower ) class TestNoteTemplate: """Test note.html template""" def test_note_has_h_entry(self, client): """Test note page has h-entry microformat""" with client.application.test_request_context(): note = create_note("# Test Note\n\nContent here.", published=True) response = client.get(f"/note/{note.slug}") assert response.status_code == 200 assert b"h-entry" in response.data def test_note_has_e_content(self, client): """Test note has e-content for content""" with client.application.test_request_context(): note = create_note("# Test Note\n\nContent here.", published=True) response = client.get(f"/note/{note.slug}") assert response.status_code == 200 assert b"e-content" in response.data def test_note_has_dt_published(self, client): """Test note has dt-published for date""" with client.application.test_request_context(): note = create_note("# Test Note\n\nContent here.", published=True) response = client.get(f"/note/{note.slug}") assert response.status_code == 200 assert b"dt-published" in response.data def test_note_has_u_url(self, client): """Test note has u-url for permalink""" with client.application.test_request_context(): note = create_note("# Test Note\n\nContent here.", published=True) response = client.get(f"/note/{note.slug}") assert response.status_code == 200 assert b"u-url" in response.data def test_note_renders_markdown(self, client): """Test note content is rendered as HTML""" with client.application.test_request_context(): note = create_note("# Heading\n\n**Bold** text.", published=True) response = client.get(f"/note/{note.slug}") assert response.status_code == 200 # Should have HTML heading assert b"<h1" in response.data # Should have bold assert b"<strong>" in response.data or b"<b>" in response.data class TestAdminTemplates: """Test admin templates""" def test_login_template_has_form(self, client): """Test login page has form""" response = client.get("/auth/login") assert response.status_code == 200 assert b"<form" in response.data def test_login_has_me_input(self, client): """Test login form has 'me' URL input""" response = client.get("/auth/login") assert response.status_code == 200 assert b'name="me"' in response.data or b'id="me"' in response.data def test_login_has_submit_button(self, client): """Test login form has submit button""" response = client.get("/auth/login") assert response.status_code == 200 assert b'type="submit"' in response.data or b"<button" in response.data def test_dashboard_extends_admin_base(self, client): """Test dashboard uses admin base template""" from starpunk.auth import create_session with client.application.test_request_context(): token = create_session("https://test.example.com") client.set_cookie("starpunk_session", token) response = client.get("/admin/") assert response.status_code == 200 # Should have admin-specific elements assert b"Dashboard" in response.data or b"Admin" in response.data def test_new_note_form_has_textarea(self, client): """Test new note form has textarea""" from starpunk.auth import create_session with client.application.test_request_context(): token = create_session("https://test.example.com") client.set_cookie("starpunk_session", token) response = client.get("/admin/new") assert response.status_code == 200 assert b"<textarea" in response.data def test_new_note_form_has_published_checkbox(self, client): """Test new note form has published checkbox""" from starpunk.auth import create_session with client.application.test_request_context(): token = create_session("https://test.example.com") client.set_cookie("starpunk_session", token) response = client.get("/admin/new") assert response.status_code == 200 assert b'type="checkbox"' in response.data def test_edit_form_prefilled(self, client): """Test edit form is prefilled with content""" from starpunk.auth import create_session with client.application.test_request_context(): token = create_session("https://test.example.com") note = create_note("# Edit Test\n\nContent.", published=True) client.set_cookie("starpunk_session", token) response = client.get(f"/admin/edit/{note.id}") assert response.status_code == 200 assert b"Edit Test" in response.data class TestFlashMessages: """Test flash message rendering""" def test_flash_message_success(self, client): """Test success flash message renders""" with client.session_transaction() as session: session["_flashes"] = [("success", "Operation successful")] response = client.get("/") assert response.status_code == 200 assert b"Operation successful" in response.data assert b"flash" in response.data or b"success" in response.data def test_flash_message_error(self, client): """Test error flash message renders""" with client.session_transaction() as session: session["_flashes"] = [("error", "An error occurred")] response = client.get("/") assert response.status_code == 200 assert b"An error occurred" in response.data assert b"flash" in response.data or b"error" in response.data def test_flash_message_warning(self, client): """Test warning flash message renders""" with client.session_transaction() as session: session["_flashes"] = [("warning", "Be careful")] response = client.get("/") assert response.status_code == 200 assert b"Be careful" in response.data def test_multiple_flash_messages(self, client): """Test multiple flash messages render""" with client.session_transaction() as session: session["_flashes"] = [ ("success", "First message"), ("error", "Second message"), ] response = client.get("/") assert response.status_code == 200 assert b"First message" in response.data assert b"Second message" in response.data class TestErrorTemplates: """Test error page templates""" def test_404_template(self, client): """Test 404 error page""" response = client.get("/nonexistent") assert response.status_code == 404 assert b"404" in response.data or b"Not Found" in response.data def test_404_has_home_link(self, client): """Test 404 page has link to homepage""" response = client.get("/nonexistent") assert response.status_code == 404 assert b'href="/"' in response.data class TestDevModeIndicator: """Test dev mode warning in templates""" def test_dev_mode_warning_shown(self, tmp_path): """Test dev mode warning appears when enabled""" test_data_dir = tmp_path / "dev_data" test_data_dir.mkdir(parents=True, exist_ok=True) test_config = { "TESTING": True, "DATABASE_PATH": test_data_dir / "starpunk.db", "DATA_PATH": test_data_dir, "NOTES_PATH": test_data_dir / "notes", "SESSION_SECRET": "test-secret", "SITE_URL": "http://localhost:5000", "DEV_MODE": True, "DEV_ADMIN_ME": "https://dev.example.com", } app = create_app(config=test_config) client = app.test_client() response = client.get("/") assert response.status_code == 200 assert b"DEVELOPMENT MODE" in response.data or b"DEV MODE" in response.data def test_dev_mode_warning_not_shown(self, client): """Test dev mode warning not shown in production""" response = client.get("/") assert response.status_code == 200 assert b"DEVELOPMENT MODE" not in response.data class TestTemplateVariables: """Test template variables are available""" def test_config_available(self, client): """Test config is available in templates""" response = client.get("/") assert response.status_code == 200 # VERSION should be rendered assert b"0.5.0" in response.data def test_site_name_available(self, client): """Test SITE_NAME is available""" response = client.get("/") assert response.status_code == 200 # Should have site name in title or header assert b"<title>" in response.data def test_url_for_works(self, client): """Test url_for generates correct URLs""" response = client.get("/") assert response.status_code == 200 # Should have URLs like /admin, /admin/login, etc. assert b"href=" in response.data class TestIndieAuthClientDiscovery: """Test IndieAuth client discovery (h-app microformats)""" def test_h_app_microformats_present(self, client): """Verify h-app client discovery markup exists""" response = client.get("/") assert response.status_code == 200 assert b'class="h-app"' in response.data def test_h_app_contains_url_and_name_properties(self, client): """Verify h-app contains u-url and p-name properties""" response = client.get("/") assert response.status_code == 200 assert b'class="u-url p-name"' in response.data def test_h_app_contains_site_url(self, client, app): """Verify h-app contains correct site URL""" response = client.get("/") assert response.status_code == 200 assert app.config["SITE_URL"].encode() in response.data def test_h_app_contains_site_name(self, client, app): """Verify h-app contains site name""" response = client.get("/") assert response.status_code == 200 site_name = app.config.get("SITE_NAME", "StarPunk").encode() assert site_name in response.data def test_h_app_is_hidden(self, client): """Verify h-app has hidden attribute for visual hiding""" response = client.get("/") assert response.status_code == 200 # h-app div should have hidden attribute assert b'class="h-app" hidden' in response.data def test_h_app_is_aria_hidden(self, client): """Verify h-app has aria-hidden for screen reader hiding""" response = client.get("/") assert response.status_code == 200 # h-app div should have aria-hidden="true" assert b'aria-hidden="true"' in response.data