This commit is contained in:
lwark
2025-09-17 07:52:16 -05:00
commit a2ff72dda8
584 changed files with 52247 additions and 0 deletions

View File

View File

View File

@@ -0,0 +1,38 @@
import os
import tempfile
from wiki.models import URLPath
from wiki.plugins.attachments import models
from tests.core.test_commands import TestManagementCommands
class TestAttachmentManagementCommands(TestManagementCommands):
"""
Add some more data
"""
def setUp(self):
super().setUp()
self.test_file = tempfile.NamedTemporaryFile(
"w", delete=False, suffix=".txt"
)
self.test_file.write("test")
self.child1 = URLPath.create_urlpath(
self.root, "test-slug", title="Test 1"
)
self.attachment1 = models.Attachment.objects.create(
article=self.child1.article
)
self.attachment1_revision1 = models.AttachmentRevision.objects.create(
attachment=self.attachment1,
file=self.test_file.name,
)
def tearDown(self):
os.unlink(self.test_file.name)
super().tearDown()

View File

@@ -0,0 +1,42 @@
from wiki.plugins.attachments.models import Attachment
from wiki.plugins.attachments.models import AttachmentRevision
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class AttachmentRevisionTests(RequireRootArticleMixin, TestBase):
def setUp(self):
super().setUp()
self.attachment = Attachment.objects.create(
article=self.root_article,
original_filename="blah.txt",
)
self.revision = AttachmentRevision.objects.create(
attachment=self.attachment,
file=None,
description="muh",
revision_number=1,
)
def test_revision_no_file(self):
# Intentionally, there are no asserts, as the test just needs to
# target an if-branch in the pre-delete signal for AttachmentRevision
self.revision.delete()
def test_revision_file_size(self):
self.assertIsNone(self.revision.get_size())
def test_get_filename_no_file(self):
self.assertIsNone(self.revision.get_filename())
def test_str(self):
self.assertEqual(
str(self.revision),
"%s: %s (r%d)"
% (
"Root Article",
"blah.txt",
1,
),
)

View File

@@ -0,0 +1,183 @@
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.urls import reverse
from wiki.models import URLPath
from ...base import ArticleWebTestUtils
from ...base import DjangoClientTestBase
from ...base import RequireRootArticleMixin
class AttachmentTests(
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
):
def setUp(self):
super().setUp()
self.article = self.root_article
self.test_data = "This is a plain text file"
self.test_description = "My file"
def _createTxtFilestream(self, strData, **kwargs):
"""
Helper function to create filestream for upload.
Parameters :
strData : str, test string data
Optional Arguments :
filename : str, Defaults to 'test.txt'
"""
filename = kwargs.get("filename", "test.txt")
data = strData.encode("utf-8")
filedata = BytesIO(data)
filestream = InMemoryUploadedFile(
filedata, None, filename, "text", len(data), None
)
return filestream
def _create_test_attachment(self, path):
url = reverse("wiki:attachments_index", kwargs={"path": path})
filestream = self._createTxtFilestream(self.test_data)
response = self.client.post(
url,
{
"description": self.test_description,
"file": filestream,
"save": "1",
},
)
self.assertRedirects(response, url)
def test_upload(self):
"""
Tests that simple file upload uploads correctly
Uploading a file should preserve the original filename.
Uploading should not modify file in any way.
"""
self._create_test_attachment("")
# Check the object was created.
attachment = self.article.shared_plugins_set.all()[0].attachment
self.assertEqual(attachment.original_filename, "test.txt")
self.assertEqual(
attachment.current_revision.file.file.read(),
self.test_data.encode("utf-8"),
)
def test_replace(self):
"""
Tests that previous revisions are not deleted
Tests that only the most recent revision is deleted when
"replace" is checked.
"""
# Upload initial file
url = reverse("wiki:attachments_index", kwargs={"path": ""})
data = "This is a plain text file"
filestream = self._createTxtFilestream(data)
self.client.post(
url, {"description": "My file", "file": filestream, "save": "1"}
)
attachment = self.article.shared_plugins_set.all()[0].attachment
# uploading for the first time should mean that there is only one revision.
self.assertEqual(attachment.attachmentrevision_set.count(), 1)
# Change url to replacement page.
url = reverse(
"wiki:attachments_replace",
kwargs={
"attachment_id": attachment.id,
"article_id": self.article.id,
},
)
# Upload replacement without removing revisions
replacement_data = data + " And this is my edit"
replacement_filestream = self._createTxtFilestream(replacement_data)
self.client.post(
url,
{
"description": "Replacement upload",
"file": replacement_filestream,
},
)
attachment = self.article.shared_plugins_set.all()[0].attachment
# Revision count should be two
self.assertEqual(attachment.attachmentrevision_set.count(), 2)
# Original filenames should not be modified
self.assertEqual(attachment.original_filename, "test.txt")
# Latest revision should equal replacment_data
self.assertEqual(
attachment.current_revision.file.file.read(),
replacement_data.encode("utf-8"),
)
first_replacement = attachment.current_revision
# Upload another replacement, this time removing most recent revision
replacement_data2 = data + " And this is a different edit"
replacement_filestream2 = self._createTxtFilestream(replacement_data2)
self.client.post(
url,
{
"description": "Replacement upload",
"file": replacement_filestream2,
"replace": "on",
},
)
attachment = self.article.shared_plugins_set.all()[0].attachment
# Revision count should still be two
self.assertEqual(attachment.attachmentrevision_set.count(), 2)
# Latest revision should equal replacment_data2
self.assertEqual(
attachment.current_revision.file.file.read(),
replacement_data2.encode("utf-8"),
)
# The first replacement should no longer be in the filehistory
self.assertNotIn(
first_replacement, attachment.attachmentrevision_set.all()
)
def test_search(self):
"""
Call the search view
"""
self._create_test_attachment("")
url = reverse("wiki:attachments_search", kwargs={"path": ""})
response = self.client.get(url, {"query": self.test_description})
self.assertContains(response, self.test_description)
def get_article(self, cont):
urlpath = URLPath.create_urlpath(
URLPath.root(), "html_attach", title="TestAttach", content=cont
)
self._create_test_attachment(urlpath.path)
return urlpath.article.render()
def test_render(self):
output = self.get_article("[attachment:1]")
expected = (
r'<span class="attachment"><a href=".*attachments/download/1/"'
r' title="Click to download test\.txt">\s*test\.txt\s*</a>'
)
self.assertRegex(output, expected)
def test_render_missing(self):
output = self.get_article("[attachment:2]")
expected = r'<span class="attachment attachment-deleted">\s*Attachment with ID #2 is deleted.\s*</span>'
self.assertRegex(output, expected)
def test_render_title(self):
output = self.get_article('[attachment:1 title:"Test title"]')
expected = (
r'<span class="attachment"><a href=".*attachments/download/1/"'
r' title="Click to download test\.txt">\s*Test title\s*</a>'
)
self.assertRegex(output, expected)
def test_render_title_size(self):
output = self.get_article('[attachment:1 title:"Test title 2" size]')
expected = (
r'<span class="attachment"><a href=".*attachments/download/1/"'
r' title="Click to download test\.txt">\s*Test title 2 \[25[^b]bytes\]\s*</a>'
)
self.assertRegex(output, expected)

View File

View File

@@ -0,0 +1,207 @@
import re
from django.urls import reverse
from django_functest import FuncBaseMixin
from wiki.models import URLPath
from ...base import DjangoClientTestBase
from ...base import RequireRootArticleMixin
from ...base import WebTestBase
TEST_CONTENT = (
"Title 1\n"
"=======\n"
"## Title 2\n"
"Title 3\n"
"-------\n"
"a\n"
"Paragraph\n"
"-------\n"
"### Title 4\n"
"## Title 5\n"
"# Title 6\n"
)
TEST_CONTENT_SRC_COMMENT = """
# Section 1
Section 1 Lorem ipsum dolor sit amet
```python
# hello world
print("hello world")
```
# Section 2
Section 2 Lorem ipsum dolor sit amet
"""
class EditSectionTests(RequireRootArticleMixin, DjangoClientTestBase):
def test_editsection(self):
# Test creating links to allow editing all sections individually
urlpath = URLPath.create_urlpath(
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
)
output = urlpath.article.render()
expected = (
r"(?s)"
r'Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-1/">\[edit\]</a>.*'
r'Title 2<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-2/">\[edit\]</a>.*'
r'Title 3<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-3/">\[edit\]</a>.*'
r'Title 4<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-4/">\[edit\]</a>.*'
r'Title 5<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-5/">\[edit\]</a>.*'
r'Title 6<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-6/">\[edit\]</a>.*'
)
self.assertRegex(output, expected)
# Test wrong header text. Editing should fail with a redirect.
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit/", "header": "does-not-exist"},
)
response = self.client.get(url)
self.assertRedirects(
response, reverse("wiki:get", kwargs={"path": "testedit/"})
)
# Test extracting sections for editing
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit/", "header": "wiki-toc-title-4"},
)
response = self.client.get(url)
expected = ">### Title 4[\r\n]*" "<"
self.assertRegex(response.rendered_content, expected)
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit/", "header": "wiki-toc-title-3"},
)
response = self.client.get(url)
expected = (
">Title 3[\r\n]*"
"-------[\r\n]*"
"a[\r\n]*"
"Paragraph[\r\n]*"
"-------[\r\n]*"
"### Title 4[\r\n]*"
"<"
)
self.assertRegex(response.rendered_content, expected)
def test_broken_content(self):
# Regression test for https://github.com/django-wiki/django-wiki/issues/1094
TEST_CONTENT = "### [Here we go](#anchor)"
urlpath = URLPath.create_urlpath(
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
)
output = urlpath.article.render()
print(output)
def get_section_content(self, response):
# extract actual section content from response (editor)
m = re.search(
r"<textarea[^>]+>(?P<content>[^<]+)</textarea>",
response.rendered_content,
re.DOTALL,
)
if m:
return m.group("content")
else:
return ""
def test_sourceblock_with_comment(self):
# https://github.com/django-wiki/django-wiki/issues/1246
URLPath.create_urlpath(
URLPath.root(),
"testedit_src",
title="TestEditSourceComment",
content=TEST_CONTENT_SRC_COMMENT,
)
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit_src/", "header": "wiki-toc-section-2"},
)
response = self.client.get(url)
actual = self.get_section_content(response)
expected = "# Section 2\r\nSection 2 Lorem ipsum dolor sit amet\r\n"
self.assertEqual(actual, expected)
def test_nonunique_headers(self):
"""test whether non-unique headers will be handled properly"""
source = """# Investigation 1\n\n## Date\n2023-01-01\n\n# Investigation 2\n\n## Date\n2023-01-02"""
URLPath.create_urlpath(
URLPath.root(),
"testedit_src",
title="TestEditSourceComment",
content=source,
)
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit_src/", "header": "wiki-toc-date"},
)
response = self.client.get(url)
actual = self.get_section_content(response)
expected = "## Date\r\n2023-01-01\r\n\r\n"
self.assertEqual(actual, expected)
url = reverse(
"wiki:editsection",
kwargs={"path": "testedit_src/", "header": "wiki-toc-date_1"},
)
response = self.client.get(url)
actual = self.get_section_content(response)
expected = "## Date\r\n2023-01-02"
self.assertEqual(actual, expected)
def test_underscore_and_dot(self):
"""test whether we can handle non-slug characters like dots in header IDs"""
# Explanation: While autogenerated ids are slugified, Markdown allows to manually
# specify the ID using the {#custom_id_value} syntax. As HTML5 only requires ID
# values not to contain whitespace, we should be able to handle any valid HTML5 ID, too.
source = """# Title 1 {#some_id_with.dot}\n\n"""
urlpath = URLPath.create_urlpath(
URLPath.root(), "testedit", title="TestEdit", content=source
)
# rendering causes NoReverseMatch without the fix
actual = urlpath.article.render()
expected = '<h1 id="some_id_with.dot">Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/some_id_with.dot/">[edit]</a></h1>'
self.assertEqual(actual, expected)
class EditSectionEditBase(RequireRootArticleMixin, FuncBaseMixin):
pass
class EditSectionEditTests(EditSectionEditBase, WebTestBase):
# Test editing a section
def test_editsection_edit(self):
urlpath = URLPath.create_urlpath(
URLPath.root(), "testedit", title="TestEdit", content=TEST_CONTENT
)
old_number = urlpath.article.current_revision.revision_number
self.get_literal_url(
reverse(
"wiki:editsection",
kwargs={"path": "testedit/", "header": "wiki-toc-title-3"},
)
)
self.fill({"#id_content": "# Header 1\nContent of the new section"})
self.submit("#id_save")
expected = (
r"(?s)"
r'Title 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-1/">\[edit\]</a>.*'
r'Title 2<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-2/">\[edit\]</a>.*'
r'Header 1<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-header-1/">\[edit\]</a>.*'
r"Content of the new section.*"
r'Title 5<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-5/">\[edit\]</a>.*'
r'Title 6<a class="article-edit-title-link" href="/testedit/_plugin/editsection/header/wiki-toc-title-6/">\[edit\]</a>.*'
)
self.assertRegex(self.last_response.content.decode("utf-8"), expected)
new_number = URLPath.objects.get(
slug="testedit"
).article.current_revision.revision_number
self.assertEqual(new_number, old_number + 1)

View File

View File

@@ -0,0 +1,99 @@
from django.urls import reverse
from django.utils import translation
from wiki.models import URLPath
from ...base import ArticleWebTestUtils
from ...base import DjangoClientTestBase
from ...base import RequireRootArticleMixin
class GlobalhistoryTests(
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
):
def test_history(self):
url = reverse("wiki:globalhistory")
url0 = reverse("wiki:globalhistory", kwargs={"only_last": "0"})
url1 = reverse("wiki:globalhistory", kwargs={"only_last": "1"})
response = self.client.get(url)
expected = "(?s).*Root Article.*no log message.*"
self.assertRegex(response.rendered_content, expected)
URLPath.create_urlpath(
URLPath.root(),
"testhistory1",
title="TestHistory1",
content="a page",
user_message="Comment 1",
)
response = self.client.get(url)
expected = (
"(?s).*TestHistory1.*Comment 1.*" "Root Article.*no log message.*"
)
self.assertRegex(response.rendered_content, expected)
urlpath = URLPath.create_urlpath(
URLPath.root(),
"testhistory2",
title="TestHistory2",
content="a page",
user_message="Comment 2",
)
expected = (
"(?s).*TestHistory2.*Comment 2.*"
"TestHistory1.*Comment 1.*"
"Root Article.*no log message.*"
)
response = self.client.get(url)
self.assertRegex(response.rendered_content, expected)
response = self.client.get(url0)
self.assertRegex(response.rendered_content, expected)
response = self.client.get(url1)
self.assertRegex(response.rendered_content, expected)
response = self.client.post(
reverse("wiki:edit", kwargs={"path": "testhistory2/"}),
{
"content": "a page modified",
"current_revision": str(urlpath.article.current_revision.id),
"preview": "0",
"save": "1",
"summary": "Testing Revision",
"title": "TestHistory2Mod",
},
)
expected = (
"(?s).*TestHistory2Mod.*Testing Revision.*"
"TestHistory2.*Comment 2.*"
"TestHistory1.*Comment 1.*"
"Root Article.*no log message.*"
)
response = self.client.get(url)
self.assertRegex(response.rendered_content, expected)
response = self.client.get(url0)
self.assertRegex(response.rendered_content, expected)
expected = (
"(?s).*TestHistory2Mod.*Testing Revision.*"
"TestHistory1.*Comment 1.*"
"Root Article.*no log message.*"
)
response = self.client.get(url1)
self.assertRegex(response.rendered_content, expected)
def test_translation(self):
# Test that translation of "List of %s changes in the wiki." exists.
url = reverse("wiki:globalhistory")
response_en = self.client.get(url)
self.assertIn("Global history", response_en.rendered_content)
self.assertIn("in the wiki", response_en.rendered_content)
with translation.override("da-DK"):
response_da = self.client.get(url)
self.assertNotIn("Global history", response_da.rendered_content)
self.assertNotIn("in the wiki", response_da.rendered_content)

View File

View File

@@ -0,0 +1,16 @@
from django.test import TestCase
from django.utils.translation import gettext
from wiki.plugins.images.forms import PurgeForm
class PurgeFormTest(TestCase):
def test_not_sure(self):
form = PurgeForm(data={"confirm": False})
self.assertIs(form.is_valid(), False)
self.assertEqual(
form.errors["confirm"], [gettext("You are not sure enough!")]
)
def test_sure(self):
form = PurgeForm(data={"confirm": True})
self.assertIs(form.is_valid(), True)

View File

@@ -0,0 +1,93 @@
import base64
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from wiki.core import markdown
from wiki.plugins.images import models
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class ImageMarkdownTests(RequireRootArticleMixin, TestBase):
def setUp(self):
super().setUp()
self.image_revision = models.ImageRevision(
image=self._create_test_gif_file(), width=1, height=1
)
self.image = models.Image(article=self.root_article)
self.image.add_revision(self.image_revision)
self.assertEqual(1, self.image.id)
def _create_test_gif_file(self):
# A black 1x1 gif
str_base64 = "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
filename = "test.gif"
data = base64.b64decode(str_base64)
filedata = BytesIO(data)
return InMemoryUploadedFile(
filedata, None, filename, "image", len(data), None
)
def test_before_and_after(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert(
"before [image:%s align:left] after" % self.image.id
)
before_pos = md_text.index("before")
figure_pos = md_text.index("<figure")
after_pos = md_text.index("after")
self.assertTrue(before_pos < figure_pos < after_pos)
def test_markdown(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert("[image:%s align:left]" % self.image.id)
self.assertIn("<figure", md_text)
self.assertNotIn("[image:%s align:left]" % self.image.id, md_text)
md_text = md.convert(
"image: [image:%s align:left]\nadasd" % self.image.id
)
self.assertIn("<figure", md_text)
self.assertIn("<figcaption", md_text)
md_text = md.convert(
"image: [image:%s align:right size:medium]\nadasd" % self.image.id
)
self.assertIn("<figure", md_text)
self.assertIn("<figcaption", md_text)
md_text = md.convert(
"image: [image:123 align:left size:medium]\nadasd"
)
self.assertIn("Image not found", md_text)
self.assertIn("<figcaption", md_text)
def test_caption(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert(
"[image:%s align:left]\n this is visual" % self.image.id
)
self.assertIn("<figure", md_text)
self.assertRegex(
md_text,
r'<figcaption class="caption">\s*this is visual\s*</figcaption>',
)
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert(
"[image:%s align:left]\n this is visual\n second line"
% self.image.id
)
self.assertIn("<figure", md_text)
self.assertRegex(
md_text,
r'<figcaption class="caption">\s*this is visual\s*second line\s*</figcaption>',
)
def check_escape(self, text_to_escape):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert("`%s`" % text_to_escape)
self.assertNotIn("<figure", md_text)
self.assertIn(text_to_escape, md_text)
def test_escape(self):
self.check_escape("[image:%s align:left]" % self.image.id)
self.check_escape("image tag: [image:%s]" % self.image.id)

View File

@@ -0,0 +1,303 @@
import base64
import os
import re
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.urls import reverse
from PIL import Image
from wiki.core.plugins import registry as plugin_registry
from wiki.models import URLPath
from wiki.plugins.images import models
from wiki.plugins.images.wiki_plugin import ImagePlugin
from ...base import ArticleWebTestUtils
from ...base import DjangoClientTestBase
from ...base import RequireRootArticleMixin
from ...base import wiki_override_settings
class ImageTests(
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
):
def setUp(self):
super().setUp()
self.article = self.root_article
# A black 1x1 gif
self.test_data = "R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
def _create_gif_filestream_from_base64(self, str_base64, **kwargs):
"""
Helper function to create filestream for upload.
Parameters :
strData : str, test string data
Optional Arguments :
filename : str, Defaults to 'test.txt'
"""
filename = kwargs.get("filename", "test.gif")
data = base64.b64decode(str_base64)
filedata = BytesIO(data)
filestream = InMemoryUploadedFile(
filedata, None, filename, "image", len(data), None
)
return filestream
def _create_test_image(self, path):
# Get the form index
plugin_index = -1
for cnt, plugin_instance in enumerate(plugin_registry.get_sidebar()):
if isinstance(plugin_instance, ImagePlugin):
plugin_index = cnt
break
self.assertGreaterEqual(
plugin_index, 0, msg="Image plugin not activated"
)
base_edit_url = reverse("wiki:edit", kwargs={"path": path})
url = base_edit_url + f"?f=form{plugin_index:d}"
filestream = self._create_gif_filestream_from_base64(self.test_data)
response = self.client.post(
url,
{
"unsaved_article_title": self.article.current_revision.title,
"unsaved_article_content": self.article.current_revision.content,
"image": filestream,
"images_save": "1",
},
)
self.assertRedirects(response, base_edit_url)
def test_index(self):
url = reverse("wiki:images_index", kwargs={"path": ""})
response = self.client.get(
url,
)
self.assertContains(response, "Images")
def test_upload(self):
"""
Tests that simple file upload uploads correctly
Uploading a file should preserve the original filename.
Uploading should not modify file in any way.
"""
self._create_test_image("")
# Check the object was created.
image = models.Image.objects.get()
image_revision = image.current_revision.imagerevision
self.assertEqual(image_revision.get_filename(), "test.gif")
self.assertEqual(
image_revision.image.file.read(), base64.b64decode(self.test_data)
)
def get_article(self, cont, image):
urlpath = URLPath.create_urlpath(
URLPath.root(), "html_image", title="TestImage", content=cont
)
if image:
self._create_test_image(urlpath.path)
return urlpath.article.render()
def test_image_missing(self):
output = self.get_article("[image:1]", False)
expected = (
'<figure class="thumbnail"><a href="">'
'<div class="caption"><em>Image not found</em></div>'
'</a><figcaption class="caption"></figcaption></figure>'
)
self.assertEqual(output, expected)
def test_image_default(self):
output = self.get_article("[image:1]", True)
image_rev = models.Image.objects.get().current_revision.imagerevision
expected = re.compile(
r'<figure class="thumbnail">'
r'<a href="' + re.escape(image_rev.image.url) + '">'
r'<img src="/?cache/.*\.jpg" alt="test\.gif">'
r'</a><figcaption class="caption"></figcaption></figure>'
)
self.assertRegex(output, expected)
def test_image_large_right(self):
output = self.get_article("[image:1 align:right size:large]", True)
image_rev = models.Image.objects.get().current_revision.imagerevision
expected = re.compile(
r'<figure class="thumbnail float-right">'
r'<a href="' + re.escape(image_rev.image.url) + '">'
r'<img src="/?cache/.*\.jpg" alt="test\.gif"></a>'
r'<figcaption class="caption"></figcaption></figure>'
)
self.assertRegex(output, expected)
def test_image_orig(self):
output = self.get_article("[image:1 size:orig]", True)
image_rev = models.Image.objects.get().current_revision.imagerevision
expected = (
'<figure class="thumbnail">'
'<a href="' + image_rev.image.url + '">'
'<img src="' + image_rev.image.url + '" alt="test.gif"></a>'
'<figcaption class="caption"></figcaption></figure>'
)
self.assertEqual(output, expected)
# https://gist.github.com/guillaumepiot/817a70706587da3bd862835c59ef584e
def generate_photo_file(self):
file = BytesIO()
image = Image.new("RGBA", size=(100, 100), color=(155, 0, 0))
image.save(file, "gif")
file.name = "test.gif"
file.seek(0)
return file
def test_add_revision(self):
self._create_test_image(path="")
image = models.Image.objects.get()
before_edit_rev = image.current_revision.revision_number
response = self.client.post(
reverse(
"wiki:images_add_revision",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
),
data={"image": self.generate_photo_file()},
)
self.assertRedirects(
response, reverse("wiki:edit", kwargs={"path": ""})
)
image = models.Image.objects.get()
self.assertEqual(models.Image.objects.count(), 1)
self.assertEqual(
image.current_revision.previous_revision.revision_number,
before_edit_rev,
)
def test_delete_restore_revision(self):
self._create_test_image(path="")
image = models.Image.objects.get()
before_edit_rev = image.current_revision.revision_number
response = self.client.get(
reverse(
"wiki:images_delete",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
),
)
self.assertRedirects(
response, reverse("wiki:images_index", kwargs={"path": ""})
)
image = models.Image.objects.get()
self.assertEqual(models.Image.objects.count(), 1)
self.assertEqual(
image.current_revision.previous_revision.revision_number,
before_edit_rev,
)
self.assertIs(image.current_revision.deleted, True)
# RESTORE
before_edit_rev = image.current_revision.revision_number
response = self.client.get(
reverse(
"wiki:images_restore",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
),
)
self.assertRedirects(
response, reverse("wiki:images_index", kwargs={"path": ""})
)
image = models.Image.objects.get()
self.assertEqual(models.Image.objects.count(), 1)
self.assertEqual(
image.current_revision.previous_revision.revision_number,
before_edit_rev,
)
self.assertFalse(image.current_revision.deleted)
def test_purge(self):
"""
Tests that an image is really purged
"""
self._create_test_image(path="")
image = models.Image.objects.get()
image_revision = image.current_revision.imagerevision
f_path = image_revision.image.file.name
self.assertIs(os.path.exists(f_path), True)
response = self.client.post(
reverse(
"wiki:images_purge",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
),
data={"confirm": True},
)
self.assertRedirects(
response, reverse("wiki:images_index", kwargs={"path": ""})
)
self.assertEqual(models.Image.objects.count(), 0)
self.assertIs(os.path.exists(f_path), False)
def test_add_revision_purge_image(self):
"""
Tests that an image with more than one revision is really purged
"""
# use another test to stage this one
self.test_add_revision()
image = models.Image.objects.get()
image_revision = image.current_revision.imagerevision
f_path = image_revision.image.file.name
self.assertIs(os.path.exists(f_path), True)
response = self.client.post(
reverse(
"wiki:images_purge",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
),
data={"confirm": True},
)
self.assertRedirects(
response, reverse("wiki:images_index", kwargs={"path": ""})
)
self.assertEqual(models.Image.objects.count(), 0)
self.assertIs(os.path.exists(f_path), False)
@wiki_override_settings(ACCOUNT_HANDLING=True)
def test_login_on_revision_add(self):
self._create_test_image(path="")
self.client.logout()
image = models.Image.objects.get()
url = reverse(
"wiki:images_add_revision",
kwargs={
"article_id": self.root_article,
"image_id": image.pk,
"path": "",
},
)
response = self.client.post(
url, data={"image": self.generate_photo_file()}
)
self.assertRedirects(
response, "{}?next={}".format(reverse("wiki:login"), url)
)

View File

View File

@@ -0,0 +1,124 @@
import markdown
from ddt import data
from ddt import ddt
from ddt import unpack
from django.test import TestCase
from django.urls import reverse_lazy
from wiki.models import URLPath
from wiki.plugins.links.mdx.djangowikilinks import WikiPathExtension
from tests.base import wiki_override_settings
FIXTURE_POSITIVE_MATCHES_TRAILING_SLASH = [
(
"[Français](wiki:/fr)",
'<p><a class="wikipath linknotfound" href="/fr/">Français</a></p>',
),
(
# Link to an existing page
"[Test link](wiki:/linktest)",
'<p><a class="wikipath" href="/linktest/">Test link</a></p>',
),
(
# Link with an empty fragment
"[Test link](wiki:/linktest#)",
'<p><a class="wikipath" href="/linktest/#/">Test link</a></p>',
),
(
# Link to a header in an existing page
"[Test head](wiki:/linktest#wiki-toc-a-section)",
'<p><a class="wikipath" href="/linktest/#wiki-toc-a-section/">Test head</a></p>',
),
(
# Link to a header in a non existing page
"[Test head nonExist](wiki:/linktesterr#wiki-toc-a-section)",
'<p><a class="wikipath linknotfound" href="/linktesterr#wiki-toc-a-section/">Test head nonExist</a></p>',
),
(
# Invalid Wiki link: The default markdown link parser takes over
"[Test head err](wiki:/linktest#wiki-toc-a-section#err)",
'<p><a href="wiki:/linktest#wiki-toc-a-section#err">Test head err</a></p>',
),
]
FIXTURE_POSITIVE_MATCHES_NO_TRAILING_SLASH = [
(
"[Français](wiki:/fr)",
'<p><a class="wikipath linknotfound" href="/fr">Français</a></p>',
),
(
# Link to an existing page
"[Test link](wiki:/linktest)",
'<p><a class="wikipath" href="/linktest">Test link</a></p>',
),
(
# Relative path
"[Test link](wiki:linktest)",
'<p><a class="wikipath" href="/linktest">Test link</a></p>',
),
(
# Link with an empty fragment
"[Test link](wiki:/linktest#)",
'<p><a class="wikipath" href="/linktest/#">Test link</a></p>',
),
(
# Link to a header in an existing page
"[Test head](wiki:/linktest#wiki-toc-a-section)",
'<p><a class="wikipath" href="/linktest/#wiki-toc-a-section">Test head</a></p>',
),
(
# Link to a header in a non existing page
"[Test head nonExist](wiki:/linktesterr#wiki-toc-a-section)",
'<p><a class="wikipath linknotfound" href="/linktesterr#wiki-toc-a-section">Test head nonExist</a></p>',
),
(
# Invalid Wiki link: The default markdown link parser takes over
"[Test head err](wiki:/linktest#wiki-toc-a-section#err)",
'<p><a href="wiki:/linktest#wiki-toc-a-section#err">Test head err</a></p>',
),
]
@ddt
class WikiPathExtensionTests(TestCase):
"""
Test the wikilinks markdown plugin.
I could not get it to work with `@pytest.mark.parametrize` so using `ddt` instead
"""
def setUp(self):
config = (("base_url", reverse_lazy("wiki:get", kwargs={"path": ""})),)
URLPath.create_root()
urlpath = URLPath.create_urlpath(
URLPath.root(),
"linktest",
title="LinkTest",
content="A page\n#A section\nA line",
user_message="Comment1",
)
# TODO: Use wiki.core.markdown.article_markdown
self.md = markdown.Markdown(
extensions=["extra", WikiPathExtension(config)]
)
self.md.article = urlpath.article
@wiki_override_settings(WIKI_WIKILINKS_TRAILING_SLASH=True)
@data(*FIXTURE_POSITIVE_MATCHES_TRAILING_SLASH)
@unpack
def test_works_with_lazy_functions_slashes(
self, markdown_input, expected_output
):
self.assertEqual(
self.md.convert(markdown_input),
expected_output,
)
@wiki_override_settings(WIKI_WIKILINKS_TRAILING_SLASH=False)
@data(*FIXTURE_POSITIVE_MATCHES_NO_TRAILING_SLASH)
@unpack
def test_works_with_lazy_functions_no_slashes(
self, markdown_input, expected_output
):
self.assertEqual(
self.md.convert(markdown_input),
expected_output,
)

View File

@@ -0,0 +1,262 @@
import html
import markdown
import pytest
from wiki.plugins.links.mdx.urlize import makeExtension
from wiki.plugins.links.mdx.urlize import UrlizeExtension
# Template accepts two strings - href value and link text value.
EXPECTED_LINK_TEMPLATE = (
'<a href="%s" rel="nofollow" target="_blank">'
'<span class="fa fa-external-link-alt">'
"</span>"
"<span>"
" %s"
"</span>"
"</a>"
)
# Template accepts two strings - href value and link text value.
EXPECTED_PARAGRAPH_TEMPLATE = "<p>%s</p>" % EXPECTED_LINK_TEMPLATE
FIXTURE_POSITIVE_MATCHES = [
# Test surrounding begin/end characters.
(
"(example.com)",
"<p>("
+ EXPECTED_LINK_TEMPLATE % ("http://example.com", "example.com")
+ ")</p>",
),
(
"<example.com>",
"<p>&lt;"
+ EXPECTED_LINK_TEMPLATE % ("http://example.com", "example.com")
+ "&gt;</p>",
),
# Test protocol specification.
(
"http://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://example.com", "http://example.com"),
),
(
"https://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("https://example.com", "https://example.com"),
),
(
"ftp://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("ftp://example.com", "ftp://example.com"),
),
(
"ftps://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("ftps://example.com", "ftps://example.com"),
),
(
"example.com",
EXPECTED_PARAGRAPH_TEMPLATE % ("http://example.com", "example.com"),
),
(
"onion://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("onion://example.com", "onion://example.com"),
),
(
"onion9+.-://example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("onion9+.-://example.com", "onion9+.-://example.com"),
),
# Test various supported host variations.
(
"10.10.1.1",
EXPECTED_PARAGRAPH_TEMPLATE % ("http://10.10.1.1", "10.10.1.1"),
),
(
"1122:3344:5566:7788:9900:aabb:ccdd:eeff",
EXPECTED_PARAGRAPH_TEMPLATE
% (
"http://1122:3344:5566:7788:9900:aabb:ccdd:eeff",
"1122:3344:5566:7788:9900:aabb:ccdd:eeff",
),
),
(
"1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
EXPECTED_PARAGRAPH_TEMPLATE
% (
"http://1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
"1122:3344:5566:7788:9900:AaBb:cCdD:EeFf",
),
),
("::1", EXPECTED_PARAGRAPH_TEMPLATE % ("http://::1", "::1")),
("1::2:3", EXPECTED_PARAGRAPH_TEMPLATE % ("http://1::2:3", "1::2:3")),
("1::", EXPECTED_PARAGRAPH_TEMPLATE % ("http://1::", "1::")),
("::", EXPECTED_PARAGRAPH_TEMPLATE % ("http://::", "::")),
(
"example.com",
EXPECTED_PARAGRAPH_TEMPLATE % ("http://example.com", "example.com"),
),
(
"example.horse",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://example.horse", "example.horse"),
),
(
"my.long.domain.example.com",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://my.long.domain.example.com", "my.long.domain.example.com"),
),
# Test port section.
(
"10.1.1.1:8000",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://10.1.1.1:8000", "10.1.1.1:8000"),
),
# Test trailing path specification.
(
"http://example.com/",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://example.com/", "http://example.com/"),
),
(
"http://example.com/my/path",
EXPECTED_PARAGRAPH_TEMPLATE
% ("http://example.com/my/path", "http://example.com/my/path"),
),
(
"http://example.com/my/path?param1=value1&param2=value2",
EXPECTED_PARAGRAPH_TEMPLATE
% (
"http://example.com/my/path?param1=value1&amp;param2=value2",
"http://example.com/my/path?param1=value1&amp;param2=value2",
),
),
# Link positioned somewhere within the text, but around whitespace boundary.
(
"This is link myhost.example.com",
"<p>This is link "
+ EXPECTED_LINK_TEMPLATE
% ("http://myhost.example.com", "myhost.example.com")
+ "</p>",
),
(
"myhost.example.com is the link",
"<p>"
+ EXPECTED_LINK_TEMPLATE
% ("http://myhost.example.com", "myhost.example.com")
+ " is the link</p>",
),
(
"I have best myhost.example.com link ever",
"<p>I have best "
+ EXPECTED_LINK_TEMPLATE
% ("http://myhost.example.com", "myhost.example.com")
+ " link ever</p>",
),
(
"I have best\nmyhost.example.com link ever",
"<p>I have best\n"
+ EXPECTED_LINK_TEMPLATE
% ("http://myhost.example.com", "myhost.example.com")
+ " link ever</p>",
),
]
FIXTURE_NEGATIVE_MATCHES = [
# localhost as part of another word.
("localhosts", "<p>localhosts</p>"),
("localhost", "<p>localhost</p>"),
("localhost:8000", "<p>localhost:8000</p>"),
# Incomplete FQDNs.
("example.", "<p>example.</p>"),
(".example .com", "<p>.example .com</p>"),
# Invalid FQDNs.
("example-.com", "<p>example-.com</p>"),
("-example.com", "<p>-example.com</p>"),
("my.-example.com", "<p>my.-example.com</p>"),
# Invalid IPv6 patterns.
(
"1:2:3:4:5:6:7:8:a", # Use :a, because using a number would match as optional port
"<p>1:2:3:4:5:6:7:8:a</p>",
),
(
"1::2::3",
"<p>1::2::3</p>",
),
(
"::::1",
"<p>::::1</p>",
),
(
"1::::",
"<p>1::::</p>",
),
# Invalid IPv4 patterns.
(
"1.2.3.4.5",
"<p>1.2.3.4.5</p>",
),
# Invalid protocols.
(
"9onion://example.com",
"<p>9onion://example.com</p>",
),
(
"-onion://example.com",
"<p>-onion://example.com</p>",
),
(
"+onion://example.com",
"<p>+onion://example.com</p>",
),
(
".onion://example.com",
"<p>.onion://example.com</p>",
),
]
class TestUrlizeExtension:
def setup_method(self):
self.md = markdown.Markdown(extensions=[UrlizeExtension()])
@pytest.mark.parametrize(
"markdown_text, expected_output", FIXTURE_POSITIVE_MATCHES
)
def test_positive_matches(self, markdown_text, expected_output):
assert self.md.convert(markdown_text) == expected_output
@pytest.mark.parametrize(
"markdown_text, expected_output", FIXTURE_NEGATIVE_MATCHES
)
def test_negative_matches(self, markdown_text, expected_output):
assert self.md.convert(markdown_text) == expected_output
def test_url_with_non_matching_begin_and_end_ignored(self):
assert self.md.convert("(example.com>") == "<p>%s</p>" % html.escape(
"(example.com>"
)
assert self.md.convert("<example.com)") == "<p>%s</p>" % html.escape(
"<example.com)"
)
assert self.md.convert("(example.com") == "<p>%s</p>" % html.escape(
"(example.com"
)
assert self.md.convert("example.com)") == "<p>%s</p>" % html.escape(
"example.com)"
)
assert self.md.convert("<example.com") == "<p>%s</p>" % html.escape(
"<example.com"
)
assert self.md.convert("example.com>") == "<p>%s</p>" % html.escape(
"example.com>"
)
def test_makeExtension_return_value():
extension = makeExtension()
assert isinstance(extension, UrlizeExtension)

View File

View File

@@ -0,0 +1,14 @@
from wiki.core import markdown
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class WikiLinksTests(RequireRootArticleMixin, TestBase):
def test_wikilink(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert("[[Root Article]]")
self.assertEqual(
md_text,
'<p><a class="wiki_wikilink wiki-broken" href="/Root_Article/">Root Article</a></p>',
)

View File

@@ -0,0 +1,18 @@
from wiki.core import markdown
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class MacroTests(RequireRootArticleMixin, TestBase):
def test_article_list(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert("[article_list depth:2]")
self.assertIn("Nothing below this level", md_text)
self.assertNotIn("[article_list depth:2]", md_text)
def test_escape(self):
md = markdown.ArticleMarkdown(article=self.root_article)
md_text = md.convert("`[article_list depth:2]`")
self.assertNotIn("Nothing below this level", md_text)
self.assertIn("[article_list depth:2]", md_text)

View File

@@ -0,0 +1,345 @@
from django.test import TestCase
from markdown import Markdown
from wiki.core import markdown
from wiki.plugins.macros.mdx.toc import WikiTocExtension
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class TocMacroTests(TestCase):
maxDiff = None
def test_toc_renders_table_of_content(self):
"""Verifies that the [TOC] wiki code renders a Table of Content"""
md = Markdown(extensions=["extra", WikiTocExtension()])
text = (
"[TOC]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc">\n'
"<ul>\n"
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.</h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection</h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_with_kwargs(self):
"""Verifies that the [TOC] wiki code renders a Table of Content"""
md = Markdown(extensions=["extra", WikiTocExtension(title="test")])
text = (
"[TOC]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">test</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.</h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection</h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
class TocMacroTestsInWiki(RequireRootArticleMixin, TestBase):
def test_toc_renders_table_of_content_in_wiki(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_with_toc_class(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC toc_class:'nontoc test']\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="nontoc test"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_with_kwargs(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC title:test]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">test</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_with_depth(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC toc_depth:1]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a></li>\n'
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_with_multi_kwargs(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC title:'test' toc_depth:'1' anchorlink:'True']\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">test</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a></li>\n'
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title"><a class="toclink" '
'href="#wiki-toc-first-title">First title.</a><a '
'class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection"><a class="toclink" '
'href="#wiki-toc-subsection">Subsection</a><a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_wrong_type(self):
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC anchorlink:Yes]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_test_bool_one(self):
# Test if the integer is 1 and should be True
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC anchorlink:1]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title"><a class="toclink" '
'href="#wiki-toc-first-title">First title.</a><a '
'class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection"><a class="toclink" '
'href="#wiki-toc-subsection">Subsection</a><a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_test_bool_zero(self):
# Test if the integer is zero and should be false
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC anchorlink:0]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)
def test_toc_renders_table_of_content_in_wiki_test_bool_wrong(self):
# Test if the integer is wrong value
md = markdown.ArticleMarkdown(article=self.root_article)
text = (
"[TOC anchorlink:5]\n"
"\n"
"# First title.\n"
"\n"
"Paragraph 1\n"
"\n"
"## Subsection\n"
"\n"
"Paragraph 2"
)
expected_output = (
'<div class="toc"><span class="toctitle">Contents</span><ul>\n'
'<li><a href="#wiki-toc-first-title">First title.</a><ul>\n'
'<li><a href="#wiki-toc-subsection">Subsection</a></li>\n'
"</ul>\n"
"</li>\n"
"</ul>\n"
"</div>\n"
'<h1 id="wiki-toc-first-title">First title.<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-first-title/">[edit]</a></h1>\n'
"<p>Paragraph 1</p>\n"
'<h2 id="wiki-toc-subsection">Subsection<a class="article-edit-title-link" '
'href="/_plugin/editsection/header/wiki-toc-subsection/">[edit]</a></h2>\n'
"<p>Paragraph 2</p>"
)
self.assertEqual(md.convert(text), expected_output)

View File

View File

@@ -0,0 +1,9 @@
from django.test import TestCase
from wiki.plugins.notifications.forms import SettingsFormSet
from tests.base import RequireSuperuserMixin
class SettingsFormTests(RequireSuperuserMixin, TestCase):
def test_formset(self):
SettingsFormSet(user=self.superuser1)

View File

@@ -0,0 +1,76 @@
from django.shortcuts import resolve_url
from django_nyt.models import Settings
from tests.base import ArticleWebTestUtils
from tests.base import DjangoClientTestBase
from tests.base import RequireRootArticleMixin
class NotificationSettingsTests(
RequireRootArticleMixin, ArticleWebTestUtils, DjangoClientTestBase
):
def setUp(self):
super().setUp()
def test_login_required(self):
self.client.logout()
response = self.client.get(resolve_url("wiki:notification_settings"))
self.assertEqual(response.status_code, 302)
def test_when_logged_in(self):
response = self.client.get(resolve_url("wiki:notification_settings"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(
response, "wiki/plugins/notifications/settings.html"
)
def test_change_settings(self):
self.settings, __ = Settings.objects.get_or_create(
user=self.superuser1, is_default=True
)
url = resolve_url("wiki:notification_settings")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
data = {"csrf_token": response.context["csrf_token"]}
# management form information, needed because of the formset
management_form = response.context["form"].management_form
for i in (
"TOTAL_FORMS",
"INITIAL_FORMS",
"MIN_NUM_FORMS",
"MAX_NUM_FORMS",
):
data[f"{management_form.prefix}-{i}"] = management_form[i].value()
for i in range(response.context["form"].total_form_count()):
# get form index 'i'
current_form = response.context["form"].forms[i]
# retrieve all the fields
for field_name in current_form.fields:
value = current_form[field_name].value()
data[f"{current_form.prefix}-{field_name}"] = (
value if value is not None else ""
)
data["form-TOTAL_FORMS"] = 1
data["form-0-email"] = 2
data["form-0-interval"] = 0
# post the request without any change
response = self.client.post(url, data, follow=True)
self.assertEqual(len(response.context.get("messages")), 1)
message = response.context.get("messages")._loaded_messages[0]
self.assertIn(
message.message,
"You will receive notifications instantly for 0 articles",
)
# Ensure we didn't create redundant Settings objects
assert self.superuser1.nyt_settings.all().count() == 1

View File

View File

@@ -0,0 +1,339 @@
from django.test import TestCase
from markdown import Markdown
from wiki.core import markdown
from wiki.plugins.pymdown import wiki_plugin
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class TocMacroTests(TestCase):
"""
This is used to test the PyMdown extensions module independently of Django Wiki. If this fails
it should because something has changed on with PyMdown or Markdown itself.
"""
def test_pymdown_renders_block_details(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = "/// details | Some summary\n" "\n" "Some content\b" "///\n"
expected_output = (
"<details>\n"
"<summary>Some summary</summary>\n"
"<p>Some content\x08///</p>\n"
"</details>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_details_with_type(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// details | Some summary\n"
" type: warning\n"
"\n"
"Some content\b"
"///\n"
)
expected_output = (
'<details class="warning">\n'
"<summary>Some summary</summary>\n"
"<p>Some content\x08///</p>\n"
"</details>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_admonition(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = "/// admonition | Some summary\n" "Some content\b" "///\n"
expected_output = (
'<div class="admonition">\n'
'<p class="admonition-title">Some summary</p>\n'
"<p>Some content\x08///</p>\n"
"</div>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_admonition_with_type(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// admonition | Some summary\n"
" type: warning\n"
"Some content\b"
"///\n"
)
expected_output = (
'<div class="admonition warning">\n'
'<p class="admonition-title">Some summary</p>\n'
"<p>Some content\x08///</p>\n"
"</div>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_definition(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// define\n"
"Apple\n"
"\n"
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Apple</dt>\n"
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
"Rosaceae.</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_definition_multiples(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// define\n"
"Apple\n"
"\n"
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
"\n"
"Orange\n"
"\n"
"- The fruit of an evergreen tree of hte genus Citrus.\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Apple</dt>\n"
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
"Rosaceae.</dd>\n"
"<dt>Orange</dt>\n"
"<dd>The fruit of an evergreen tree of hte genus Citrus.</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_definition_multiple_terms(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// define\n"
"Term 1\n"
"\n"
"Term 2\n"
"- Definition a\n"
"\n"
"Term 3\n"
"\n"
"- Definition b\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Term 1</dt>\n"
"<dt>Term 2\n"
"- Definition a</dt>\n"
"<dt>Term 3</dt>\n"
"<dd>Definition b</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_renders_block_html_wrap(self):
extensions = ["extra"]
extensions.extend(wiki_plugin.PymdownPlugin.markdown_extensions)
md = Markdown(extensions=extensions)
text = (
"/// html | div[stype='border: 1px solid red;']\n"
"some *markdown* content\n"
"///\n"
)
expected_output = (
'<div stype="border: 1px solid red;">\n'
"<p>some <em>markdown</em> content</p>\n"
"</div>"
)
self.assertEqual(expected_output, md.convert(text))
class TocMacroTestsInWiki(RequireRootArticleMixin, TestBase):
def test_pymdown_in_wiki_renders_block_details(self):
wiki_plugin.settings.update_whitelist() # Fixes testing bug
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = "/// details | Some summary\n" "\n" "Some content\n" "///\n"
expected_output = (
"<details>\n"
"<summary>Some summary</summary>\n"
"<p>Some content</p>\n"
"</details>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_details_with_type(self):
wiki_plugin.settings.update_whitelist() # Fixes testing bug
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// details | Some summary\n"
" type: warning\n"
"Some content\n"
"///\n"
)
expected_output = (
'<details class="warning">\n'
"<summary>Some summary</summary>\n"
"<p>Some content</p>\n"
"</details>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_admonition(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = "/// admonition | Some summary\n" "Some content.\n" "///\n"
expected_output = (
'<div class="admonition">\n'
'<p class="admonition-title">Some summary</p>\n'
"<p>Some content.</p>\n"
"</div>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_admonition_with_type(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// admonition | Some summary\n"
" type: warning\n"
"Some content.\n"
"///\n"
)
expected_output = (
'<div class="admonition warning">\n'
'<p class="admonition-title">Some summary</p>\n'
"<p>Some content.</p>\n"
"</div>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_definition(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// define\n"
"Apple\n"
"\n"
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Apple</dt>\n"
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
"Rosaceae.</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_definition_multiples(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// define\n"
"Apple\n"
"\n"
"- Pomaceous fruit of plants of the genus Malu in the family Rosaceae.\n"
"\n"
"Orange\n"
"\n"
"- The fruit of an evergreen tree of hte genus Citrus.\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Apple</dt>\n"
"<dd>Pomaceous fruit of plants of the genus Malu in the family "
"Rosaceae.</dd>\n"
"<dt>Orange</dt>\n"
"<dd>The fruit of an evergreen tree of hte genus Citrus.</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_definition_multiple_terms(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// define\n"
"Term 1\n"
"\n"
"Term 2\n"
"- Definition a\n"
"\n"
"Term 3\n"
"\n"
"- Definition b\n"
"///\n"
)
expected_output = (
"<dl>\n"
"<dt>Term 1</dt>\n"
"<dt>Term 2\n"
"- Definition a</dt>\n"
"<dt>Term 3</dt>\n"
"<dd>Definition b</dd>\n"
"</dl>"
)
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_html_wrap(self):
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = "/// html | div.my-class\n" "some *markdown* content\n" "///\n"
expected_output = '<div class="my-class">\n<p>some <em>markdown</em> content</p>\n</div>'
self.assertEqual(expected_output, md.convert(text))
def test_pymdown_in_wiki_renders_block_html_wrap_test_bleach(self):
"""
The tags get bleached and thus this doesn't work.
"""
md = markdown.ArticleMarkdown(
article=self.root_article,
extensions=wiki_plugin.PymdownPlugin.markdown_extensions,
)
text = (
"/// html | div[stype='border: 1px solid red;']\n"
"some *markdown* content\n"
"///\n"
)
expected_output = (
"<div>\n<p>some <em>markdown</em> content</p>\n</div>"
)
self.assertEqual(expected_output, md.convert(text))

View File

View File

@@ -0,0 +1,97 @@
from django.urls import reverse
from wiki.core import markdown
from wiki.models import URLPath
from ...base import wiki_override_settings
from tests.base import RequireRootArticleMixin
from tests.base import TestBase
class RedlinksTests(RequireRootArticleMixin, TestBase):
def setUp(self):
super().setUp()
self.child = URLPath.create_urlpath(self.root, "child")
def test_root_to_self(self):
self.assert_internal(self.root, "[Internal](./)")
def test_root_to_child(self):
self.assert_internal(self.root, "[Child](child/)")
def test_child_to_self(self):
self.assert_internal(self.child, "[Child](../child/)")
def test_child_to_self_no_slash(self):
self.assert_internal(self.child, "[Child](../child)")
def test_root_to_outside(self):
self.assert_external(
self.root, "[Outside](http://outside.example.org/)"
)
def test_absolute_external(self):
if reverse("wiki:get", kwargs={"path": ""}) == "/":
# All absolute paths could be wiki-internal, and the server root is
# the the wiki root, which is bound to exist.
self.assert_internal(self.root, "[Server Root](/)")
else:
# The wiki root is below the server root, so the server root is an
# external link.
self.assert_external(self.root, "[Server Root](/)")
self.assert_external(self.root, "[Static File](/static/)")
def test_absolute_internal(self):
wiki_root = reverse("wiki:get", kwargs={"path": ""})
self.assert_internal(self.root, f"[Server Root]({wiki_root})")
def test_child_to_broken(self):
self.assert_broken(self.child, "[Broken](../broken/)")
def test_root_to_broken(self):
self.assert_broken(self.root, "[Broken](broken/)")
def test_not_a_link(self):
self.assert_none(self.root, '<a id="anchor">old-style anchor</a>')
def test_invalid_url(self):
self.assert_none(self.root, "[Invalid](http://127[.500.20.1/)")
def test_mailto(self):
self.assert_none(self.root, "<foo@example.com>")
def assert_none(self, urlpath, md_text):
md = markdown.ArticleMarkdown(article=urlpath.article)
html = md.convert(md_text)
self.assertNotIn("wiki-internal", html)
self.assertNotIn("wiki-external", html)
self.assertNotIn("wiki-broken", html)
self.assertIn("<a", html)
def assert_internal(self, urlpath, md_text):
md = markdown.ArticleMarkdown(article=urlpath.article)
html = md.convert(md_text)
self.assertIn("wiki-internal", html)
self.assertNotIn("wiki-external", html)
self.assertNotIn("wiki-broken", html)
def assert_external(self, urlpath, md_text):
md = markdown.ArticleMarkdown(article=urlpath.article)
html = md.convert(md_text)
self.assertNotIn("wiki-internal", html)
self.assertIn("wiki-external", html)
self.assertNotIn("wiki-broken", html)
def assert_broken(self, urlpath, md_text):
md = markdown.ArticleMarkdown(article=urlpath.article)
html = md.convert(md_text)
self.assertNotIn("wiki-internal", html)
self.assertNotIn("wiki-external", html)
self.assertIn("wiki-broken", html)
@wiki_override_settings(
WIKI_URL_CONFIG_CLASS="tests.core.test_models.WikiCustomUrlPatterns",
ROOT_URLCONF="tests.core.test_urls",
)
class RedLinksWithChangedBaseURL(RedlinksTests):
pass