Add collapsible distinct org headers and top-of-file Inbox

This commit is contained in:
2026-08-07 21:11:35 -04:00
parent 7817336754
commit fa57387008
6 changed files with 694 additions and 55 deletions

192
test_main.py Normal file
View File

@ -0,0 +1,192 @@
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 &#x27;world&#x27;</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(">&#x25B8;</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 &quot;clients first, flag flips\nsecond.&quot; 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()