Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
295 lines
9.4 KiB
Python
295 lines
9.4 KiB
Python
"""Unit tests for the Lexical JSON → sx converter."""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import os
|
|
import pytest
|
|
|
|
# Add project root so shared.sx.html_to_sx resolves, plus the ghost dir.
|
|
_project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
sys.path.insert(0, _project_root)
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "bp", "blog", "ghost"))
|
|
from lexical_to_sx import lexical_to_sx
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _doc(*children):
|
|
"""Wrap children in a minimal Lexical document."""
|
|
return {"root": {"children": list(children)}}
|
|
|
|
|
|
def _text(s, fmt=0):
|
|
return {"type": "text", "text": s, "format": fmt}
|
|
|
|
|
|
def _paragraph(*children):
|
|
return {"type": "paragraph", "children": list(children)}
|
|
|
|
|
|
def _heading(tag, *children):
|
|
return {"type": "heading", "tag": tag, "children": list(children)}
|
|
|
|
|
|
def _link(url, *children):
|
|
return {"type": "link", "url": url, "children": list(children)}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Basic text
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBasicText:
|
|
def test_empty_doc(self):
|
|
result = lexical_to_sx(_doc())
|
|
assert result == '(<> (p ""))'
|
|
|
|
def test_single_paragraph(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("Hello"))))
|
|
assert result == '(p "Hello")'
|
|
|
|
def test_two_paragraphs(self):
|
|
result = lexical_to_sx(_doc(
|
|
_paragraph(_text("Hello")),
|
|
_paragraph(_text("World")),
|
|
))
|
|
assert "(p " in result
|
|
assert '"Hello"' in result
|
|
assert '"World"' in result
|
|
|
|
def test_heading(self):
|
|
result = lexical_to_sx(_doc(_heading("h2", _text("Title"))))
|
|
assert result == '(h2 "Title")'
|
|
|
|
def test_h3(self):
|
|
result = lexical_to_sx(_doc(_heading("h3", _text("Sub"))))
|
|
assert result == '(h3 "Sub")'
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Formatting
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestFormatting:
|
|
def test_bold(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("hi", 1))))
|
|
assert "(strong " in result
|
|
|
|
def test_italic(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("hi", 2))))
|
|
assert "(em " in result
|
|
|
|
def test_strikethrough(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("hi", 4))))
|
|
assert "(s " in result
|
|
|
|
def test_bold_italic(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("hi", 3))))
|
|
assert "(strong " in result
|
|
assert "(em " in result
|
|
|
|
def test_code(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("x", 16))))
|
|
assert "(code " in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Links
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLinks:
|
|
def test_link(self):
|
|
result = lexical_to_sx(_doc(
|
|
_paragraph(_link("https://example.com", _text("click")))
|
|
))
|
|
assert '(a :href "https://example.com"' in result
|
|
assert '"click"' in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lists
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLists:
|
|
def test_unordered_list(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "list", "listType": "bullet",
|
|
"children": [
|
|
{"type": "listitem", "children": [_text("one")]},
|
|
{"type": "listitem", "children": [_text("two")]},
|
|
]
|
|
}))
|
|
assert "(ul " in result
|
|
assert "(li " in result
|
|
assert '"one"' in result
|
|
|
|
def test_ordered_list(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "list", "listType": "number",
|
|
"children": [
|
|
{"type": "listitem", "children": [_text("first")]},
|
|
]
|
|
}))
|
|
assert "(ol " in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Block elements
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestBlocks:
|
|
def test_hr(self):
|
|
result = lexical_to_sx(_doc({"type": "horizontalrule"}))
|
|
assert result == "(hr)"
|
|
|
|
def test_quote(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "quote", "children": [_text("wisdom")]
|
|
}))
|
|
assert '(blockquote "wisdom")' == result
|
|
|
|
def test_codeblock(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "codeblock", "code": "print('hi')", "language": "python"
|
|
}))
|
|
assert '(pre (code :class "language-python"' in result
|
|
assert "print" in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cards
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCards:
|
|
def test_image(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "image", "src": "photo.jpg", "alt": "test"
|
|
}))
|
|
assert '(~kg_cards/kg-image :src "photo.jpg" :alt "test")' == result
|
|
|
|
def test_image_wide_with_caption(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "image", "src": "p.jpg", "alt": "",
|
|
"cardWidth": "wide", "caption": "Fig 1"
|
|
}))
|
|
assert ':width "wide"' in result
|
|
assert ':caption "Fig 1"' in result
|
|
|
|
def test_image_html_caption(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "image", "src": "p.jpg", "alt": "",
|
|
"caption": 'Photo by <a href="https://x.com">Author</a>'
|
|
}))
|
|
assert ':caption (<> "Photo by " (a :href "https://x.com" "Author"))' in result
|
|
|
|
def test_bookmark(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "bookmark", "url": "https://example.com",
|
|
"metadata": {"title": "Example", "description": "A site"}
|
|
}))
|
|
assert "(~kg_cards/kg-bookmark " in result
|
|
assert ':url "https://example.com"' in result
|
|
assert ':title "Example"' in result
|
|
|
|
def test_callout(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "callout", "backgroundColor": "blue",
|
|
"calloutEmoji": "💡",
|
|
"children": [_text("Note")]
|
|
}))
|
|
assert "(~kg_cards/kg-callout " in result
|
|
assert ':color "blue"' in result
|
|
|
|
def test_button(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "button", "buttonText": "Click",
|
|
"buttonUrl": "https://example.com"
|
|
}))
|
|
assert "(~kg_cards/kg-button " in result
|
|
assert ':text "Click"' in result
|
|
|
|
def test_toggle(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "toggle", "heading": "FAQ",
|
|
"children": [_text("Answer")]
|
|
}))
|
|
assert "(~kg_cards/kg-toggle " in result
|
|
assert ':heading "FAQ"' in result
|
|
|
|
def test_html(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "html", "html": "<div>custom</div>"
|
|
}))
|
|
assert result == '(~kg_cards/kg-html (div "custom"))'
|
|
|
|
def test_embed(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "embed", "html": "<iframe></iframe>",
|
|
"caption": "Video"
|
|
}))
|
|
assert "(~kg_cards/kg-embed " in result
|
|
assert ':caption "Video"' in result
|
|
|
|
def test_markdown(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "markdown", "markdown": "**bold** text"
|
|
}))
|
|
assert result.startswith("(~kg_cards/kg-md ")
|
|
assert "(p " in result
|
|
assert "(strong " in result
|
|
|
|
def test_video(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "video", "src": "v.mp4", "cardWidth": "wide"
|
|
}))
|
|
assert "(~kg_cards/kg-video " in result
|
|
assert ':width "wide"' in result
|
|
|
|
def test_audio(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "audio", "src": "s.mp3", "title": "Song", "duration": 195
|
|
}))
|
|
assert "(~kg_cards/kg-audio " in result
|
|
assert ':duration "3:15"' in result
|
|
|
|
def test_file(self):
|
|
result = lexical_to_sx(_doc({
|
|
"type": "file", "src": "f.pdf", "fileName": "doc.pdf",
|
|
"fileSize": 2100000
|
|
}))
|
|
assert "(~kg_cards/kg-file " in result
|
|
assert ':filename "doc.pdf"' in result
|
|
assert "MB" in result
|
|
|
|
def test_paywall(self):
|
|
result = lexical_to_sx(_doc({"type": "paywall"}))
|
|
assert result == "(~kg_cards/kg-paywall)"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Escaping
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestEscaping:
|
|
def test_quotes_in_text(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text('He said "hello"'))))
|
|
assert '\\"hello\\"' in result
|
|
|
|
def test_backslash_in_text(self):
|
|
result = lexical_to_sx(_doc(_paragraph(_text("a\\b"))))
|
|
assert "a\\\\b" in result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JSON string input
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestJsonString:
|
|
def test_string_input(self):
|
|
import json
|
|
doc = _doc(_paragraph(_text("test")))
|
|
result = lexical_to_sx(json.dumps(doc))
|
|
assert '(p "test")' == result
|