;;; test-pearl-convert.el --- Tests for the markdown->org conversion -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Craig Jennings ;; Author: Craig Jennings ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Tests for `pearl--md-to-org' and `pearl--md-line-to-org' -- ;; the pure-elisp markdown->org conversion tier. Cover each supported ;; construct (links, inline code, bold, underscore italics, headings, bullets, ;; fenced code), the heading-safety guard, and literal pass-through. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el")) ;;; inline conversion (ert-deftest test-pearl-convert-link () "A markdown link becomes an org link with url and label swapped." (should (string= "see [[https://x.y][the docs]] now" (pearl--md-line-to-org "see [the docs](https://x.y) now")))) (ert-deftest test-pearl-convert-inline-code () "Inline code backticks become org verbatim tildes." (should (string= "call ~foo()~ here" (pearl--md-line-to-org "call `foo()` here")))) (ert-deftest test-pearl-convert-bold () "Markdown bold becomes org bold." (should (string= "a *strong* word" (pearl--md-line-to-org "a **strong** word")))) (ert-deftest test-pearl-convert-italic-underscore () "Underscore italics become org italics, but identifiers are left alone." (should (string= "an /emphatic/ point" (pearl--md-line-to-org "an _emphatic_ point"))) (should (string= "the foo_bar_baz name" (pearl--md-line-to-org "the foo_bar_baz name")))) ;;; line / block conversion (ert-deftest test-pearl-convert-heading-to-bold () "A markdown heading becomes a bold line, never an org heading." (let ((out (pearl--md-to-org "## Big Heading"))) (should (string= "*Big Heading*" out)) (should-not (string-match-p "^\\*+ " out)))) (ert-deftest test-pearl-convert-bullets () "Markdown `*' and `+' bullets become org `-' bullets." (should (string= "- one\n- two" (pearl--md-to-org "* one\n+ two")))) (ert-deftest test-pearl-convert-fenced-code () "A fenced code block becomes a src block, verbatim inside." (should (string= "#+begin_src elisp\n(+ 1 2)\n#+end_src" (pearl--md-to-org "```elisp\n(+ 1 2)\n```")))) (ert-deftest test-pearl-convert-code-block-is-verbatim () "Inline markup inside a fenced block is not converted." (let ((out (pearl--md-to-org "```\n**not bold** here\n```"))) (should (string-match-p "\\*\\*not bold\\*\\*" out)))) (ert-deftest test-pearl-convert-guards-heading-line () "A non-bullet line that Org would read as a heading is space-guarded." (let ((out (pearl--md-to-org "** looks like a heading"))) (should-not (string-match-p "^\\*+ " out)) (should (string-prefix-p " " out)))) (ert-deftest test-pearl-convert-passes-through-plain-and-tables () "Plain text and unsupported constructs (tables) pass through unchanged." (should (string= "just some text" (pearl--md-to-org "just some text"))) (should (string= "| a | b |\n|---|---|" (pearl--md-to-org "| a | b |\n|---|---|")))) (ert-deftest test-pearl-convert-empty () "An empty or nil description converts to the empty string." (should (string= "" (pearl--md-to-org ""))) (should (string= "" (pearl--md-to-org nil)))) ;;; org -> markdown (the push direction) (ert-deftest test-pearl-org-to-md-link () "An org link becomes a markdown link with label and url swapped back." (should (string= "see [the docs](https://x.y) now" (pearl--org-line-to-md "see [[https://x.y][the docs]] now")))) (ert-deftest test-pearl-org-to-md-bare-link () "An org link with no description becomes the bare url." (should (string= "visit https://x.y" (pearl--org-line-to-md "visit [[https://x.y]]")))) (ert-deftest test-pearl-org-to-md-inline-code () "Org verbatim tildes become markdown backticks." (should (string= "call `foo()` here" (pearl--org-line-to-md "call ~foo()~ here")))) (ert-deftest test-pearl-org-to-md-bold () "Org bold becomes markdown bold." (should (string= "a **strong** word" (pearl--org-line-to-md "a *strong* word")))) (ert-deftest test-pearl-org-to-md-italic () "Org italics become underscore italics, but paths are left alone." (should (string= "an _emphatic_ point" (pearl--org-line-to-md "an /emphatic/ point"))) (should (string= "the /usr/local/bin path" (pearl--org-line-to-md "the /usr/local/bin path")))) (ert-deftest test-pearl-org-to-md-fenced-code () "An org src block becomes a fenced code block, language preserved." (should (string= "```elisp\n(+ 1 2)\n```" (pearl--org-to-md "#+begin_src elisp\n(+ 1 2)\n#+end_src")))) (ert-deftest test-pearl-org-to-md-code-block-is-verbatim () "Org markup inside a src block is not converted back." (let ((out (pearl--org-to-md "#+begin_src\n*not bold* here\n#+end_src"))) (should (string-match-p "\\*not bold\\* here" out)))) (ert-deftest test-pearl-org-to-md-quote-block () "An org quote block becomes markdown blockquote lines." (should (string= "> a quote\n> second line" (pearl--org-to-md "#+begin_quote\na quote\nsecond line\n#+end_quote")))) (ert-deftest test-pearl-org-to-md-checkbox-case () "Org uppercase checkbox marks normalize to markdown lowercase." (should (string= "- [ ] todo\n- [x] done" (pearl--org-to-md "- [ ] todo\n- [X] done")))) (ert-deftest test-pearl-org-to-md-empty () "An empty or nil body converts to the empty string." (should (string= "" (pearl--org-to-md ""))) (should (string= "" (pearl--org-to-md nil)))) (ert-deftest test-pearl-org-to-md-passes-through-tables () "Tables and unsupported constructs pass through unchanged." (should (string= "| a | b |\n|---|---|" (pearl--org-to-md "| a | b |\n|---|---|")))) ;;; round-trip: org-to-md inverts md-to-org for the supported subset (ert-deftest test-pearl-convert-roundtrip-identity () "For the cleanly-supported constructs, org->md(md->org(x)) == x. Markdown headings and single-asterisk italics are intentionally lossy (see the conversion-tier docstring) and are excluded here." (dolist (md '("a **strong** word" "call `foo()` here" "an _emphatic_ point" "see [the docs](https://x.y) now" "- one\n- two\n- three" "1. first\n2. second" "- [ ] todo\n- [x] done" "```elisp\n(+ 1 2)\n```" "just some plain prose" "| a | b |\n|---|---|")) (should (string= md (pearl--org-to-md (pearl--md-to-org md)))))) (ert-deftest test-pearl-convert-roundtrip-boundary-cases () "Round-trip identity holds across markdown boundary cases — the property that keeps a fetch + no-edit push from changing the remote. Two of these round-trip safely despite an imperfect intermediate Org form: a URL containing parens, and inline code whose contents look like markdown (rendered converted in the Org view, but reversed exactly on push)." (dolist (md '("[a](u1) and [b](u2)" ; multiple links on a line "see [docs](http://x.com/(p))" ; parens inside the URL "`**b** and [l](u) and _i_`" ; markdown-looking inline code "café — naïve 日本語 ✓" ; non-ASCII prose "before\n```\nunclosed code\nmore code" ; unclosed fence, no swallow "text\n```python\nx = 1\n```\nafter")) ; fence with trailing text (should (string= md (pearl--org-to-md (pearl--md-to-org md)))))) (provide 'test-pearl-convert) ;;; test-pearl-convert.el ends here