import unittest
from unittest.mock import MagicMock, patch
import django
from django.utils.safestring import SafeText, mark_safe
from common import components
from games.models import Platform, Game, Purchase, Session
class ComponentIntegrationTest(unittest.TestCase):
"""Test Component() works correctly with caching transparent."""
def test_tag_name_component(self):
result = components.Component(
tag_name="div",
attributes=[("class", "test")],
children="hello",
)
self.assertEqual(result, '
hello
')
class ComponentCacheTest(unittest.TestCase):
"""Component rendering is memoized via _render_element."""
def setUp(self):
components._render_element.cache_clear()
def test_identical_components_hit_cache(self):
components.Component(tag_name="div", attributes=[("class", "x")], children="hi")
misses = components._render_element.cache_info().misses
components.Component(tag_name="div", attributes=[("class", "x")], children="hi")
info = components._render_element.cache_info()
self.assertEqual(info.misses, misses) # no new miss
self.assertGreaterEqual(info.hits, 1) # served from cache
def test_cache_is_bounded(self):
self.assertEqual(
components._render_element.cache_parameters()["maxsize"], 4096
)
def test_safe_and_unsafe_children_do_not_collide(self):
"""A SafeText "" and a plain "" are equal as strings but must
render differently — the cache key must keep them distinct."""
safe = components.Component(tag_name="span", children=[mark_safe("x")])
unsafe = components.Component(tag_name="span", children=["x"])
self.assertIn("x", safe)
self.assertIn("<b>x</b>", unsafe)
self.assertNotEqual(safe, unsafe)
class RandomidDeterministicTest(unittest.TestCase):
"""Test that randomid() produces deterministic, reproducible IDs."""
def test_same_content_same_id(self):
r1 = components.randomid(content="foo")
r2 = components.randomid(content="foo")
self.assertEqual(r1, r2)
def test_different_content_different_id(self):
r1 = components.randomid(content="foo")
r2 = components.randomid(content="bar")
self.assertNotEqual(r1, r2)
def test_seed_prepended(self):
result = components.randomid(seed="a", content="x")
self.assertTrue(result.startswith("a"))
def test_seed_respects_length(self):
result = components.randomid(seed="ab", content="x", length=10)
self.assertEqual(len(result), 10)
def test_empty_input_returns_empty(self):
self.assertEqual(components.randomid(), "")
def test_output_is_lowercase_alphanum(self):
result = components.randomid(content="test")
self.assertTrue(
all(c in "abcdefghijklmnopqrstuvwxyz0123456789" for c in result)
)
def test_output_length_is_correct(self):
for length in [5, 10, 15, 20]:
result = components.randomid(content="test", length=length)
self.assertEqual(len(result), length)
def test_hash_reproducible_across_calls(self):
results = [components.randomid(content="reproducible_test") for _ in range(100)]
self.assertEqual(len(set(results)), 1)
class RandomidVsOldBehaviorTest(unittest.TestCase):
"""Prove the new hash-based approach is deterministic while the old random approach was not."""
def _old_random_id(self, seed="", length=10):
from random import choices
from string import ascii_lowercase
return seed + "".join(choices(ascii_lowercase, k=length))
def test_old_random_produces_different_ids(self):
results = [self._old_random_id() for _ in range(50)]
self.assertEqual(len(set(results)), 50)
def test_new_hash_produces_same_id(self):
results = [components.randomid(content="determinism_test") for _ in range(50)]
self.assertEqual(len(set(results)), 1)
def test_new_hash_deterministic_per_content(self):
results = [components.randomid(content=c) for c in ["aaa", "bbb", "ccc"]]
self.assertEqual(len(set(results)), 3)
class PopoverDeterministicTest(unittest.TestCase):
"""Test that Popover() produces deterministic HTML output."""
def test_same_popover_same_id(self):
r1 = components.Popover("hello", wrapped_content="hello")
r2 = components.Popover("hello", wrapped_content="hello")
self.assertEqual(r1, r2)
def test_different_content_different_id(self):
r1 = components.Popover("content_a", wrapped_content="content_a")
r2 = components.Popover("content_b", wrapped_content="content_b")
self.assertNotEqual(r1, r2)
def test_wrapped_classes_affect_id(self):
r1 = components.Popover("c", wrapped_content="c", wrapped_classes="class_x")
r2 = components.Popover("c", wrapped_content="c", wrapped_classes="class_y")
self.assertNotEqual(r1, r2)
def test_wrapped_content_affects_id(self):
r1 = components.Popover("popover", wrapped_content="wrapped_a")
r2 = components.Popover("popover", wrapped_content="wrapped_b")
self.assertNotEqual(r1, r2)
def test_popover_content_affects_id(self):
r1 = components.Popover("popover_a", wrapped_content="wrapped")
r2 = components.Popover("popover_b", wrapped_content="wrapped")
self.assertNotEqual(r1, r2)
def test_full_html_deterministic(self):
r1 = components.Popover("hello world", wrapped_content="hello world")
r2 = components.Popover("hello world", wrapped_content="hello world")
self.assertEqual(r1.encode(), r2.encode())
class TemplatetagRandomidTest(unittest.TestCase):
"""Test games/templatetags/randomid.py produces deterministic IDs."""
def test_same_seed_same_id(self):
from games.templatetags import randomid
r1 = randomid.randomid(seed="foo")
r2 = randomid.randomid(seed="foo")
self.assertEqual(r1, r2)
def test_different_seed_different_id(self):
from games.templatetags import randomid
r1 = randomid.randomid(seed="foo")
r2 = randomid.randomid(seed="bar")
self.assertNotEqual(r1, r2)
def test_output_length_ten(self):
from games.templatetags import randomid
for seed in ["a", "hello", "test1234"]:
result = randomid.randomid(seed=seed)
self.assertEqual(len(result), 10)
def test_empty_seed_returns_hash(self):
from games.templatetags import randomid
result = randomid.randomid()
self.assertEqual(len(result), 10)
self.assertTrue(all(c in "abcdef0123456789" for c in result))
class ComponentReturnTypeTest(unittest.TestCase):
"""Test that component functions return SafeText and render correctly."""
def test_div_returns_safe_text(self):
result = components.Div([("class", "x")], "hello")
self.assertIsInstance(result, SafeText)
def test_div_deterministic(self):
r1 = components.Div([("class", "x")], "hello")
r2 = components.Div([("class", "x")], "hello")
self.assertEqual(r1, r2)
self.assertIn('hello
', r1)
def test_div_no_args(self):
result = components.Div(children="test")
self.assertIsInstance(result, SafeText)
self.assertIn("test
", result)
def test_a_returns_safe_text(self):
result = components.A([], "link")
self.assertIsInstance(result, SafeText)
def test_a_literal_href(self):
result = components.A([], "x", href="/literal/path")
self.assertIn('href="/literal/path"', result)
def test_a_url_name_reversed(self):
from unittest.mock import patch
with patch("common.components.reverse", return_value="/resolved/url"):
result = components.A([], "link", url_name="some_name")
self.assertIn('href="/resolved/url"', result)
def test_a_no_url_or_href(self):
result = components.A([], "link")
self.assertIn("link", result)
self.assertNotIn("href=", result)
def test_a_both_url_name_and_href_raises(self):
with self.assertRaises(ValueError):
components.A(href="/path", url_name="some_name")
def test_button_returns_safe_text(self):
result = components.Button([], "click")
self.assertIsInstance(result, SafeText)
self.assertIn("