193 lines
8.7 KiB
Python
193 lines
8.7 KiB
Python
import unittest
|
|
import main
|
|
|
|
|
|
class TestOrgParser(unittest.TestCase):
|
|
def setUp(self):
|
|
self.known_paths = {"note1.org", "note2.org"}
|
|
self.id_to_path = {"id-123": "note1.org"}
|
|
self.path_to_slug = {"note1.org": "id-123", "note2.org": "note2.org"}
|
|
|
|
def render(self, content, show_webhook=False):
|
|
return main.render_org_to_html(
|
|
content=content,
|
|
source_relative_path="source.org",
|
|
known_paths=self.known_paths,
|
|
id_to_path=self.id_to_path,
|
|
path_to_slug=self.path_to_slug,
|
|
show_webhook=show_webhook,
|
|
)
|
|
|
|
def test_external_links(self):
|
|
content = "See [[https://hungryroot.atlassian.net/browse/BE-7924][BE-7924]] for details."
|
|
rendered = self.render(content)
|
|
self.assertIn("<a class='org-link' href='https://hungryroot.atlassian.net/browse/BE-7924' target='_blank' rel='noopener noreferrer'>BE-7924</a>", rendered)
|
|
|
|
def test_internal_links(self):
|
|
content = "Check [[note1.org][Note 1]] or [[id:id-123][Note 1 ID]]."
|
|
rendered = self.render(content)
|
|
self.assertIn("<a class='org-link' href='/n/id-123'>Note 1</a>", rendered)
|
|
self.assertIn("<a class='org-link' href='/n/id-123'>Note 1 ID</a>", rendered)
|
|
|
|
def test_inline_formatting(self):
|
|
content = "Added flat replacements (=author_id=, ~python~, *always present*, /italic text/, _underlined_, +deleted+)."
|
|
rendered = self.render(content)
|
|
self.assertIn("<code class='org-code'>author_id</code>", rendered)
|
|
self.assertIn("<code class='org-code'>python</code>", rendered)
|
|
self.assertIn("<strong>always present</strong>", rendered)
|
|
self.assertIn("<em>italic text</em>", rendered)
|
|
self.assertIn("<u>underlined</u>", rendered)
|
|
self.assertIn("<del>deleted</del>", rendered)
|
|
|
|
def test_code_blocks(self):
|
|
content = "#+begin_src python\ndef hello():\n return 'world'\n#+end_src"
|
|
rendered = self.render(content)
|
|
self.assertIn("<pre class='org-src-block'><code class='language-python'>def hello():\n return 'world'</code></pre>", rendered)
|
|
|
|
def test_tables(self):
|
|
content = (
|
|
"| Field | List default |\n"
|
|
"|-------+--------------|\n"
|
|
"| =author= | present |\n"
|
|
)
|
|
rendered = self.render(content)
|
|
self.assertIn("<table class='org-table'>", rendered)
|
|
self.assertIn("<thead><tr><th>Field</th><th>List default</th></tr></thead>", rendered)
|
|
self.assertIn("<tbody><tr><td><code class='org-code'>author</code></td><td>present</td></tr></tbody>", rendered)
|
|
|
|
def test_lists(self):
|
|
content = "- =GET /api/v3/cookbooks/=\n- =GET /api/v3/cookbooks/<slug>/="
|
|
rendered = self.render(content)
|
|
self.assertIn("<ul class='org-list'>", rendered)
|
|
self.assertIn("<li><code class='org-code'>GET /api/v3/cookbooks/</code></li>", rendered)
|
|
|
|
ordered_content = "1. Item one\n2. Item two"
|
|
ordered_rendered = self.render(ordered_content)
|
|
self.assertIn("<ol class='org-list'>", ordered_rendered)
|
|
self.assertIn("<li>Item one</li>", ordered_rendered)
|
|
|
|
def test_comments_ignored(self):
|
|
content = "#+CREATED: [2026-08-04]\n# This is a comment\nReal text"
|
|
rendered = self.render(content)
|
|
self.assertNotIn("#+CREATED", rendered)
|
|
self.assertNotIn("This is a comment", rendered)
|
|
self.assertIn("<p>Real text</p>", rendered)
|
|
|
|
def test_headings_with_todo_and_tags(self):
|
|
content = "* TODO Fix auth issue :bug:backend:"
|
|
rendered = self.render(content)
|
|
self.assertIn("<h2", rendered)
|
|
self.assertIn("<span class='org-todo org-todo-todo'>TODO</span>", rendered)
|
|
self.assertIn("Fix auth issue", rendered)
|
|
self.assertIn("<span class='org-tag'>bug</span>", rendered)
|
|
self.assertIn("<span class='org-tag'>backend</span>", rendered)
|
|
|
|
def test_headings_build_collapsible_sections(self):
|
|
content = (
|
|
"#+TITLE: Doc title\n"
|
|
"* Top\n"
|
|
"Some content.\n"
|
|
"** Sub\n"
|
|
"Deeper content.\n"
|
|
"* Sibling\n"
|
|
"Sibling content.\n"
|
|
)
|
|
rendered = self.render(content)
|
|
self.assertIn("<h1>Doc title</h1>", rendered)
|
|
self.assertIn("<section class='org-section' data-level='2'>", rendered)
|
|
self.assertIn("<section class='org-section collapsed' data-level='3'>", rendered)
|
|
self.assertIn("class='org-heading'", rendered)
|
|
self.assertIn("class='org-collapse-toggle'", rendered)
|
|
self.assertIn("class='org-section-body'", rendered)
|
|
self.assertIn("<p>Some content.</p>", rendered)
|
|
self.assertIn("<p>Deeper content.</p>", rendered)
|
|
self.assertIn("<p>Sibling content.</p>", rendered)
|
|
self.assertNotIn("<section class='org-section' data-level='2'></section>", rendered)
|
|
|
|
def test_level3_and_deeper_start_collapsed(self):
|
|
content = (
|
|
"* One\n"
|
|
"** Sub\n"
|
|
"Body A\n"
|
|
"*** Deep\n"
|
|
"Body B\n"
|
|
"* Two\n"
|
|
"Body C\n"
|
|
)
|
|
rendered = self.render(content)
|
|
self.assertIn("<section class='org-section' data-level='2'>", rendered)
|
|
self.assertIn("<section class='org-section collapsed' data-level='3'>", rendered)
|
|
self.assertIn("<section class='org-section collapsed' data-level='4'>", rendered)
|
|
self.assertIn("aria-expanded='false'", rendered)
|
|
self.assertIn(">▸</button>", rendered)
|
|
|
|
def test_nested_section_contains_deeper_heading(self):
|
|
content = "* Top\n** Sub\nDeep text\n* End\nTail text"
|
|
rendered = self.render(content)
|
|
top = rendered.index("<section class='org-section' data-level='2'>")
|
|
sub = rendered.index("<section class='org-section collapsed' data-level='3'>")
|
|
end = rendered.index("<section class='org-section' data-level='2'>", top + 1)
|
|
self.assertTrue(top < sub < end)
|
|
|
|
def test_heading_stars_capped_at_level_6(self):
|
|
content = "****** Deep heading\nBody text"
|
|
rendered = self.render(content)
|
|
self.assertIn("<h6 class='org-heading' data-level='6'>", rendered)
|
|
self.assertIn("<p>Body text</p>", rendered)
|
|
|
|
def test_multiline_emphasis(self):
|
|
content = '*The safe ordering is always "clients first, flag flips\nsecond." Until then.*'
|
|
rendered = self.render(content)
|
|
self.assertIn(
|
|
"<strong>The safe ordering is always "clients first, flag flips\nsecond." Until then.</strong>",
|
|
rendered,
|
|
)
|
|
|
|
def test_code_with_internal_marker(self):
|
|
content = "Request =?expand=pairing_ids,product_ids= on the list endpoint."
|
|
rendered = self.render(content)
|
|
self.assertIn("<code class='org-code'>?expand=pairing_ids,product_ids</code>", rendered)
|
|
|
|
def test_soft_wrapped_paragraph_is_single_p(self):
|
|
content = "First line of prose\nsecond line of prose\n\nNext paragraph"
|
|
rendered = self.render(content)
|
|
self.assertIn("<p>First line of prose\nsecond line of prose</p>", rendered)
|
|
self.assertIn("<p>Next paragraph</p>", rendered)
|
|
|
|
def test_add_to_inbox_creates_inbox_at_top_after_preamble(self):
|
|
content = "#+TITLE: Notes\n* Work\nSome text.\n"
|
|
updated = main.add_to_inbox(content, "Call dentist")
|
|
lines = updated.splitlines()
|
|
self.assertEqual(lines[0], "#+TITLE: Notes")
|
|
self.assertEqual(lines[1], "* Inbox")
|
|
self.assertEqual(lines[2], "** TODO Call dentist")
|
|
self.assertEqual(lines[3], "* Work")
|
|
self.assertEqual(lines[4], "Some text.")
|
|
|
|
def test_add_to_inbox_appends_to_existing_inbox(self):
|
|
content = "* Inbox\n** TODO Existing\n* Work\n"
|
|
updated = main.add_to_inbox(content, "New task")
|
|
lines = updated.splitlines()
|
|
self.assertEqual(lines[0], "* Inbox")
|
|
self.assertEqual(lines[1], "** TODO Existing")
|
|
self.assertEqual(lines[2], "** TODO New task")
|
|
self.assertEqual(lines[3], "* Work")
|
|
|
|
def test_add_to_inbox_moves_existing_inbox_to_top(self):
|
|
content = "* Work\nSome text.\n* Inbox\n** TODO Old task\n"
|
|
updated = main.add_to_inbox(content, "New task")
|
|
lines = updated.splitlines()
|
|
self.assertEqual(lines[0], "* Inbox")
|
|
self.assertEqual(lines[1], "** TODO Old task")
|
|
self.assertEqual(lines[2], "** TODO New task")
|
|
self.assertEqual(lines[3], "* Work")
|
|
self.assertEqual(lines[4], "Some text.")
|
|
|
|
def test_add_to_inbox_blank_text_is_noop(self):
|
|
content = "* Inbox\n** TODO Keep\n"
|
|
self.assertEqual(main.add_to_inbox(content, " "), content)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|