mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 05:11:10 -05:00
283 lines
9.6 KiB
Python
283 lines
9.6 KiB
Python
"""
|
|
Tests for HTMX utility functions.
|
|
|
|
These tests verify that the HTMX response helpers generate
|
|
correct responses with proper headers and content.
|
|
"""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from django.http import HttpRequest
|
|
from django.test import RequestFactory
|
|
|
|
from apps.core.htmx_utils import (
|
|
get_htmx_target,
|
|
get_htmx_trigger,
|
|
htmx_error,
|
|
htmx_modal_close,
|
|
htmx_redirect,
|
|
htmx_refresh,
|
|
htmx_refresh_section,
|
|
htmx_success,
|
|
htmx_trigger,
|
|
htmx_validation_response,
|
|
htmx_warning,
|
|
is_htmx_request,
|
|
)
|
|
|
|
|
|
class TestIsHtmxRequest:
|
|
"""Tests for is_htmx_request function."""
|
|
|
|
def test_returns_true_for_htmx_request(self):
|
|
"""Should return True when HX-Request header is 'true'."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/", HTTP_HX_REQUEST="true")
|
|
assert is_htmx_request(request) is True
|
|
|
|
def test_returns_false_for_regular_request(self):
|
|
"""Should return False for regular requests without HTMX header."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/")
|
|
assert is_htmx_request(request) is False
|
|
|
|
def test_returns_false_for_wrong_value(self):
|
|
"""Should return False when HX-Request header has wrong value."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/", HTTP_HX_REQUEST="false")
|
|
assert is_htmx_request(request) is False
|
|
|
|
|
|
class TestGetHtmxTarget:
|
|
"""Tests for get_htmx_target function."""
|
|
|
|
def test_returns_target_when_present(self):
|
|
"""Should return target ID when HX-Target header is present."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/", HTTP_HX_TARGET="my-target")
|
|
assert get_htmx_target(request) == "my-target"
|
|
|
|
def test_returns_none_when_missing(self):
|
|
"""Should return None when HX-Target header is missing."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/")
|
|
assert get_htmx_target(request) is None
|
|
|
|
|
|
class TestGetHtmxTrigger:
|
|
"""Tests for get_htmx_trigger function."""
|
|
|
|
def test_returns_trigger_when_present(self):
|
|
"""Should return trigger ID when HX-Trigger header is present."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/", HTTP_HX_TRIGGER="my-button")
|
|
assert get_htmx_trigger(request) == "my-button"
|
|
|
|
def test_returns_none_when_missing(self):
|
|
"""Should return None when HX-Trigger header is missing."""
|
|
factory = RequestFactory()
|
|
request = factory.get("/")
|
|
assert get_htmx_trigger(request) is None
|
|
|
|
|
|
class TestHtmxRedirect:
|
|
"""Tests for htmx_redirect function."""
|
|
|
|
def test_sets_redirect_header(self):
|
|
"""Should set HX-Redirect header with correct URL."""
|
|
response = htmx_redirect("/parks/")
|
|
assert response["HX-Redirect"] == "/parks/"
|
|
|
|
def test_returns_empty_body(self):
|
|
"""Should return empty response body."""
|
|
response = htmx_redirect("/parks/")
|
|
assert response.content == b""
|
|
|
|
|
|
class TestHtmxTrigger:
|
|
"""Tests for htmx_trigger function."""
|
|
|
|
def test_simple_trigger(self):
|
|
"""Should set simple trigger name."""
|
|
response = htmx_trigger("myEvent")
|
|
assert response["HX-Trigger"] == "myEvent"
|
|
|
|
def test_trigger_with_payload(self):
|
|
"""Should set trigger with JSON payload."""
|
|
response = htmx_trigger("myEvent", {"key": "value"})
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
assert trigger_data == {"myEvent": {"key": "value"}}
|
|
|
|
|
|
class TestHtmxRefresh:
|
|
"""Tests for htmx_refresh function."""
|
|
|
|
def test_sets_refresh_header(self):
|
|
"""Should set HX-Refresh header to 'true'."""
|
|
response = htmx_refresh()
|
|
assert response["HX-Refresh"] == "true"
|
|
|
|
|
|
class TestHtmxSuccess:
|
|
"""Tests for htmx_success function."""
|
|
|
|
def test_basic_success_message(self):
|
|
"""Should create success response with toast trigger."""
|
|
response = htmx_success("Item saved!")
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert "showToast" in trigger_data
|
|
assert trigger_data["showToast"]["type"] == "success"
|
|
assert trigger_data["showToast"]["message"] == "Item saved!"
|
|
assert trigger_data["showToast"]["duration"] == 5000
|
|
|
|
def test_success_with_custom_duration(self):
|
|
"""Should allow custom duration."""
|
|
response = htmx_success("Quick message", duration=2000)
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["duration"] == 2000
|
|
|
|
def test_success_with_title(self):
|
|
"""Should include title when provided."""
|
|
response = htmx_success("Details here", title="Success!")
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["title"] == "Success!"
|
|
|
|
def test_success_with_action(self):
|
|
"""Should include action button config."""
|
|
response = htmx_success(
|
|
"Item deleted",
|
|
action={"label": "Undo", "onClick": "undoDelete()"},
|
|
)
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["action"]["label"] == "Undo"
|
|
assert trigger_data["showToast"]["action"]["onClick"] == "undoDelete()"
|
|
|
|
def test_success_with_html_content(self):
|
|
"""Should include HTML in response body."""
|
|
response = htmx_success("Done", html="<div>Updated</div>")
|
|
assert response.content == b"<div>Updated</div>"
|
|
|
|
|
|
class TestHtmxError:
|
|
"""Tests for htmx_error function."""
|
|
|
|
def test_basic_error_message(self):
|
|
"""Should create error response with toast trigger."""
|
|
response = htmx_error("Something went wrong")
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert response.status_code == 400
|
|
assert trigger_data["showToast"]["type"] == "error"
|
|
assert trigger_data["showToast"]["message"] == "Something went wrong"
|
|
assert trigger_data["showToast"]["duration"] == 0 # Persistent by default
|
|
|
|
def test_error_with_custom_status(self):
|
|
"""Should allow custom HTTP status code."""
|
|
response = htmx_error("Validation failed", status=422)
|
|
assert response.status_code == 422
|
|
|
|
def test_error_with_retry_action(self):
|
|
"""Should include retry action when requested."""
|
|
response = htmx_error("Server error", show_retry=True)
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["action"]["label"] == "Retry"
|
|
|
|
|
|
class TestHtmxWarning:
|
|
"""Tests for htmx_warning function."""
|
|
|
|
def test_basic_warning_message(self):
|
|
"""Should create warning response with toast trigger."""
|
|
response = htmx_warning("Session expiring soon")
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["type"] == "warning"
|
|
assert trigger_data["showToast"]["message"] == "Session expiring soon"
|
|
assert trigger_data["showToast"]["duration"] == 8000
|
|
|
|
|
|
class TestHtmxModalClose:
|
|
"""Tests for htmx_modal_close function."""
|
|
|
|
def test_basic_modal_close(self):
|
|
"""Should trigger closeModal event."""
|
|
response = htmx_modal_close()
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["closeModal"] is True
|
|
|
|
def test_modal_close_with_message(self):
|
|
"""Should include success toast when message provided."""
|
|
response = htmx_modal_close(message="Saved successfully!")
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["closeModal"] is True
|
|
assert trigger_data["showToast"]["message"] == "Saved successfully!"
|
|
|
|
def test_modal_close_with_refresh(self):
|
|
"""Should include refresh section trigger."""
|
|
response = htmx_modal_close(
|
|
message="Done",
|
|
refresh_target="#items-list",
|
|
refresh_url="/items/",
|
|
)
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["refreshSection"]["target"] == "#items-list"
|
|
assert trigger_data["refreshSection"]["url"] == "/items/"
|
|
|
|
|
|
class TestHtmxRefreshSection:
|
|
"""Tests for htmx_refresh_section function."""
|
|
|
|
def test_sets_retarget_header(self):
|
|
"""Should set HX-Retarget header."""
|
|
response = htmx_refresh_section("#my-section", html="<div>New</div>")
|
|
assert response["HX-Retarget"] == "#my-section"
|
|
assert response["HX-Reswap"] == "innerHTML"
|
|
|
|
def test_includes_success_message(self):
|
|
"""Should include toast when message provided."""
|
|
response = htmx_refresh_section(
|
|
"#my-section",
|
|
html="<div>New</div>",
|
|
message="Section updated",
|
|
)
|
|
trigger_data = json.loads(response["HX-Trigger"])
|
|
|
|
assert trigger_data["showToast"]["message"] == "Section updated"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
class TestHtmxValidationResponse:
|
|
"""Tests for htmx_validation_response function."""
|
|
|
|
def test_validation_error_response(self):
|
|
"""Should render error template with errors."""
|
|
response = htmx_validation_response(
|
|
"email",
|
|
errors=["Invalid email format"],
|
|
)
|
|
assert response.status_code == 200
|
|
# Response should contain error markup
|
|
|
|
def test_validation_success_response(self):
|
|
"""Should render success template with message."""
|
|
response = htmx_validation_response(
|
|
"username",
|
|
success_message="Username available",
|
|
)
|
|
assert response.status_code == 200
|
|
# Response should contain success markup
|
|
|
|
def test_validation_neutral_response(self):
|
|
"""Should render empty success when no errors or message."""
|
|
response = htmx_validation_response("field")
|
|
assert response.status_code == 200
|