import copy from unittest import skipIf from unittest.mock import patch, MagicMock from django.test import TestCase try: from cms.api import create_page, add_plugin from cms.test_utils.testcases import CMSTestCase from lxml.etree import Element from djangocms_text import html, settings from djangocms_text.html import NH3Parser, dynamic_href, dynamic_src, render_dynamic_attributes SKIP_CMS_TEST = False except ModuleNotFoundError: from django.test.utils import TestCase as CMSTestCase SKIP_CMS_TEST = True from tests.fixtures import DJANGO_CMS4, TestFixture @skipIf(SKIP_CMS_TEST, "Skipping tests because djangocms is not installed") class SanitizerTestCase(TestCase): def test_sanitizer(self): body = 'some text' body = html.clean_html(body) self.assertTrue('data-one="1"' in body) self.assertTrue('data-two="2"' in body) def test_sanitizer_with_custom_token_parser(self): cleaner = NH3Parser(additional_attributes={"span": {"donut"}}) body = 'some text' body = html.clean_html(body, cleaner=cleaner) self.assertEqual('some text', body) def test_sanitizer_without_token_parsers(self): body = 'some text' body = html.clean_html(body, cleaner=NH3Parser(generic_attribute_prefixes=set())) self.assertEqual("some text", body) def test_attribute_merging(self): # Backup the original global attributes. original_global_attributes = copy.deepcopy(getattr(settings, "TEXT_ADDITIONAL_ATTRIBUTES", {})) # Configure global attributes. settings.TEXT_ADDITIONAL_ATTRIBUTES = {"span": {"global-attr"}} # Instantiate NH3Parser with instance-specific additional attributes. parser = NH3Parser(additional_attributes={"span": {"local-attr"}}) input_html = 'text' cleaned = html.clean_html(input_html, cleaner=parser) # Assert both attributes are present in the cleaned output. self.assertIn('global-attr="g"', cleaned) self.assertIn('local-attr="l"', cleaned) # Restore the original global settings. settings.TEXT_ADDITIONAL_ATTRIBUTES = original_global_attributes @skipIf(SKIP_CMS_TEST, "Skipping tests because djangocms is not installed") class HtmlSanitizerAdditionalProtocolsTests(CMSTestCase): def test_default_tag_removal(self): settings.TEXT_ADDITIONAL_ATTRIBUTES = {} text = html.clean_html( '', cleaner=NH3Parser(), ) self.assertNotIn("iframe", NH3Parser().ALLOWED_TAGS) self.assertEqual( "", text, ) def test_custom_tag_enabled(self): text = html.clean_html( '', cleaner=NH3Parser( additional_attributes={"iframe": {"src"}}, ), ) self.assertEqual( '', text, ) def test_default_attribute_escaping(self): text = html.clean_html( 'foo', cleaner=NH3Parser(), ) self.assertEqual( "foo", text, ) def test_custom_attribute_enabled(self): text = html.clean_html( 'foo', cleaner=NH3Parser( additional_attributes={ "span": {"test-attr"}, }, ), ) self.assertEqual( 'foo', text, ) def test_default_protocol_removal(self): settings.TEXT_ADDITIONAL_PROTOCOLS = [] text = html.clean_html( '', cleaner=NH3Parser(), ) self.assertEqual("", text) def test_custom_protocol_enabled(self): settings.TEXT_ADDITIONAL_PROTOCOLS = ["rtmp"] text = html.clean_html('', cleaner=NH3Parser()) self.assertEqual('', text) def test_clean_html_with_sanitize_enabled(self): old_text_html_sanitize = settings.TEXT_HTML_SANITIZE settings.TEXT_HTML_SANITIZE = True original = 'foo' cleaned = html.clean_html( original, ) try: self.assertHTMLEqual("foo", cleaned) finally: settings.TEXT_HTML_SANITIZE = old_text_html_sanitize def test_clean_html_with_sanitize_disabled(self): old_text_html_sanitize = settings.TEXT_HTML_SANITIZE settings.TEXT_HTML_SANITIZE = False original = 'foo' cleaned = html.clean_html( original, ) try: self.assertHTMLEqual(original, cleaned) finally: settings.TEXT_HTML_SANITIZE = old_text_html_sanitize def test_clean_html_preserves_aria_attributes(self): original = 'foo' cleaned = html.clean_html( original, ) self.assertHTMLEqual(original, cleaned) def test_clean_html_preserves_data_attributes(self): original = 'foo' cleaned = html.clean_html( original, ) self.assertHTMLEqual(original, cleaned) def test_clean_html_preserves_role_attribute(self): original = 'foo' cleaned = html.clean_html( original, ) self.assertHTMLEqual(original, cleaned) @skipIf(SKIP_CMS_TEST, "Skipping tests because djangocms is not installed") class HTMLDynamicAttriutesTest(TestFixture, CMSTestCase): def test_dynamic_link(self): page = self.create_page("page", "page.html", language="en") self.publish(page, "en") self.assertEqual( page.get_absolute_url(), "/en/page/", ) dynamic_html = f'Link' result = render_dynamic_attributes(dynamic_html) self.assertEqual( result, f'Link', ) def test_invalid_dynamic_link(self): page = self.create_page("page", "page.html", language="en") self.publish(page, "en") self.assertEqual( page.get_absolute_url(), "/en/page/", ) dynamic_html = 'Link' result = render_dynamic_attributes(dynamic_html) self.assertEqual( result, 'Link', ) @skipIf(SKIP_CMS_TEST, "Skipping tests because djangocms is not installed") class DynamicAttributesTestCase(TestCase): @patch("djangocms_text.html.apps.get_model") def test_dynamic_href_sets_correct_attribute(self, mock_get_model): mock_obj = MagicMock() mock_obj.get_absolute_url.return_value = "/test-url" mock_get_model.return_value.objects.filter.return_value = [mock_obj] elem = Element("a", {"data-cms-href": "app.Model:1"}) dynamic_href(elem, mock_obj, "href") self.assertEqual(elem.attrib["href"], "/test-url") @patch("djangocms_text.html.apps.get_model") def test_dynamic_src_sets_correct_attribute(self, mock_get_model): mock_obj = MagicMock() mock_obj.get_absolute_url.return_value = "/test-url" mock_get_model.return_value.objects.filter.return_value = [mock_obj] elem = Element("img", {"data-cms-src": "app.Model:1"}) dynamic_src(elem, mock_obj, "src") self.assertEqual(elem.attrib["src"], "/test-url") def test_render_dynamic_attributes_changes_html(self): page = create_page("page", "page.html", language="en") html = f'Link' updated_html = render_dynamic_attributes(html) self.assertIn('Link', updated_html) def test_render_dynamic_attributes_fails(self): html = 'Link' updated_html = render_dynamic_attributes(html) self.assertIn('Link', updated_html) def test_render_dynamic_attributes_fails_edit_mode(self): html = 'Link' updated_html = render_dynamic_attributes(html, admin_objects=True) self.assertIn('Link', updated_html) def test_render_dynamic_attributes_handles_no_dynamic_attributes(self): html = "

No dynamic attributes

" updated_html = render_dynamic_attributes(html) self.assertEqual(html, updated_html) def save_image(filename, image, parent_plugin, width, height): pass @skipIf(SKIP_CMS_TEST, "Skipping tests because djangocms is not installed") class DjangoCMSPictureIntegrationTestCase(CMSTestCase): def setUp(self): super().setUp() self.home = self.create_homepage("home", "page.html", "en") if DJANGO_CMS4: self.placeholder = ( self.home.pagecontent_set(manager="admin_manager").first().get_placeholders().get(slot="content") ) else: self.placeholder = self.home.get_placeholders().get(slot="content") def test_extract_images(self): with patch("tests.test_html.save_image") as mock_save_image: add_plugin( self.placeholder, "TextPlugin", "en", body='', ) mock_save_image.assert_called_once()