From 4d89296f81b3e15aa7e71d5ad1e50ac30af0e3e5 Mon Sep 17 00:00:00 2001 From: Manthan Surkar Date: Sun, 24 May 2026 02:47:09 +0530 Subject: [PATCH] Fix paragraph_close collapsing paragraph spacing (#15) paragraph_close returned a single \n, so two paragraphs in the source markdown rendered as adjacent lines in Slack instead of separated by a blank line. This diverged from the JS reference slackify-markdown, which separates paragraphs with \n\n. Changes: - paragraph_close now returns \n\n for normal paragraphs and \n for tight-list paragraphs (markdown-it marks those with token.hidden). - render() trims trailing newlines and re-adds a single \n so the document ends with exactly one trailing \n, matching the JS lib. This also fixes a sibling divergence where headings at end of document trailed with \n\n. - Tests updated to assert the JS-reference outputs; test_complex_markdown regenerated from actual output. --- src/slackify_markdown/slackify.py | 9 +++++++-- tests/test_convert.py | 27 +++++++++++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/slackify_markdown/slackify.py b/src/slackify_markdown/slackify.py index 1c75a60..489cae9 100644 --- a/src/slackify_markdown/slackify.py +++ b/src/slackify_markdown/slackify.py @@ -64,7 +64,8 @@ def render( if token.type in self.SUPPORTED_TOKENS: final_tokens.append(token) - return super().render(final_tokens, options, env) + rendered = super().render(final_tokens, options, env) + return rendered.rstrip("\n") + "\n" def hardbreak( self, @@ -323,7 +324,11 @@ def paragraph_close( options: Dict[str, Any], env: Dict[str, Any], ) -> str: - return "\n" + # Tight-list items have hidden paragraph tokens; they only need a + # single newline between items, not a blank-line block separator. + if tokens[idx].hidden: + return "\n" + return "\n\n" def blockquote_open( self, diff --git a/tests/test_convert.py b/tests/test_convert.py index 5b4bf72..caf6c00 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -33,13 +33,13 @@ def test_user_group_mentions(): def test_bold_italics_strike_bullet_list(): markdown = "- **Bold** and _Italic_ with ~~strike~~.\n\n- **Bold** and _Italic_ with ~~strike~~.\n\n" - expected = "• *Bold* and _Italic_ with ~strike~.\n• *Bold* and _Italic_ with ~strike~.\n" + expected = "• *Bold* and _Italic_ with ~strike~.\n\n• *Bold* and _Italic_ with ~strike~.\n" assert slackify_markdown(markdown) == expected def test_bold_italics_strike_ordered_list(): markdown = "1. **Bold** and _Italic_ with ~~strike~~.\n\n2. **Bold** and _Italic_ with ~~strike~~.\n\n" - expected = "1. *Bold* and _Italic_ with ~strike~.\n2. *Bold* and _Italic_ with ~strike~.\n" + expected = "1. *Bold* and _Italic_ with ~strike~.\n\n2. *Bold* and _Italic_ with ~strike~.\n" assert slackify_markdown(markdown) == expected @@ -111,6 +111,7 @@ def greet(name): expected_slack_format = """*Project Overview* Welcome to the *Project X* documentation. This project aims to revolutionize the industry by introducing: + • _Innovative solutions_ • *Cutting-edge technology* • ~Disruptive strategies~ @@ -128,16 +129,20 @@ def greet(name): *Code Example* Here's a simple Python function: + ``` def greet(name): return f"Hello, {name}!" ``` > "Code is like humor. When you have to explain it, it’s bad." – _Cory House_ + *Links and Images* For more information, visit our . + + *Table* | Feature | Description | @@ -145,9 +150,11 @@ def greet(name): | Speed | Fast performance | | Usability | Easy to use | | Security | Top-notch protection| + *Footnotes* This project is a game-changer[^1]. + [^1]: According to industry experts. """ @@ -168,26 +175,26 @@ def test_escaped_text(): def test_definitions(): mrkdown = "hello\n\n[1]: http://atlassian.com\n\nworld\n\n[2]: http://atlassian.com" - slack = "hello\nworld\n" + slack = "hello\n\nworld\n" assert slackify_markdown(mrkdown) == slack def test_headings(): mrkdown = "# heading 1\n## heading 2\n### heading 3" - slack = "*heading 1*\n\n*heading 2*\n\n*heading 3*\n\n" + slack = "*heading 1*\n\n*heading 2*\n\n*heading 3*\n" assert slackify_markdown(mrkdown) == slack def test_heading_with_bold(): - assert slackify_markdown("### **Step 1**: Description here") == "*Step 1: Description here*\n\n" - assert slackify_markdown("### **Step 1**") == "*Step 1*\n\n" - assert slackify_markdown("# **Test**: text") == "*Test: text*\n\n" - assert slackify_markdown("### Normal and **bold**") == "*Normal and bold*\n\n" + assert slackify_markdown("### **Step 1**: Description here") == "*Step 1: Description here*\n" + assert slackify_markdown("### **Step 1**") == "*Step 1*\n" + assert slackify_markdown("# **Test**: text") == "*Test: text*\n" + assert slackify_markdown("### Normal and **bold**") == "*Normal and bold*\n" def test_heading_with_italic(): - assert slackify_markdown("### *emphasized*") == "*_emphasized_*\n\n" - assert slackify_markdown("### ***bold italic***") == "*_bold italic_*\n\n" + assert slackify_markdown("### *emphasized*") == "*_emphasized_*\n" + assert slackify_markdown("### ***bold italic***") == "*_bold italic_*\n" def test_bold():