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("", result) self.assertIn("", result) def test_raw_html_children_are_escaped(self): result = components.Component( tag_name="div", children=[""] ) self.assertNotIn("