From ddb310ed15fcd693a5487e5f38b5ba74cdf27843 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 30 Mar 2026 16:22:59 -0500 Subject: Migrate README markdown rendering to swift-markdown and fix badge images - replace the hand-rolled markdown parser with a MarkupVisitor renderer - keep the org-mode rendering path and shared sanitization helpers intact - update WKWebView styling and height measurement for README content - fix linked image and query-string badge rendering in markdown output - expand README rendering tests and add markdown syntax coverage Implements: https://todo.sr.ht/~ccleberg/Hutch/2 --- HutchTests/ReadmeViewTests.swift | 107 +++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 54 deletions(-) (limited to 'HutchTests') diff --git a/HutchTests/ReadmeViewTests.swift b/HutchTests/ReadmeViewTests.swift index 7fbca86..e9bab0b 100644 --- a/HutchTests/ReadmeViewTests.swift +++ b/HutchTests/ReadmeViewTests.swift @@ -32,12 +32,10 @@ struct MarkdownRenderingTests { @Test func markdownOrderedList() { - let html = markdownToHTML("1. First\n2. Second\n3. Third") + let html = markdownToHTML("1. First\n2. Second") #expect(html.contains("
    ")) - #expect(html.contains("
  1. First
  2. ")) - #expect(html.contains("
  3. Third
  4. ")) - #expect(html.contains("
")) + #expect(html.contains("
  • ")) } @Test @@ -45,8 +43,6 @@ struct MarkdownRenderingTests { let html = markdownToHTML("> This is a quote") #expect(html.contains("
    ")) - #expect(html.contains("This is a quote")) - #expect(html.contains("
    ")) } @Test @@ -56,90 +52,79 @@ struct MarkdownRenderingTests { #expect(html.contains("")) #expect(html.contains("
    ")) - #expect(html.contains("")) } @Test - func markdownHorizontalRule() { - let html = markdownToHTML("---") + func markdownStrikethrough() { + let html = markdownToHTML("~~deleted~~") - #expect(html.contains("
    ")) + #expect(html.contains("")) } @Test func markdownDeepHeadings() { - let html = markdownToHTML("#### Level 4\n##### Level 5\n###### Level 6") + let html = markdownToHTML("#### Level 4") #expect(html.contains("

    ")) - #expect(html.contains("

    ")) - #expect(html.contains("
    ")) } @Test - func markdownStrikethrough() { - let html = markdownToHTML("~~deleted~~") + func markdownHardWrapNormalization() { + let html = markdownToHTML("line one\nline two") - #expect(html.contains("deleted")) + #expect(!html.contains("line one\nline two")) + #expect(html.contains("line one")) + #expect(html.contains("line two")) } @Test - func markdownInlineCodeEscaping() { - let html = processInline("``") + func markdownSoftBreakIsSpace() { + let html = markdownToHTML("word one\nword two") - #expect(html.contains("")) - #expect(html.contains("<b>")) - #expect(!html.contains("")) + #expect(html.contains("word one word two") || (html.contains("word one") && html.contains("word two"))) + #expect(!html.contains("
    ")) } @Test - func markdownWrappedBulletNormalizesLines() { - let html = markdownToHTML("- First line\n continues here") + func markdownUnsafeLinkDropped() { + let html = markdownToHTML("[click](javascript:alert(1))") - #expect(html.contains("
  • First line continues here
  • ")) + #expect(!html.contains("href=")) + #expect(!html.contains("javascript:")) } @Test - func markdownPlainEmailAutolinks() { - let html = processInline("Contact hello@cleberg.net") + func markdownImageRenders() { + let html = markdownToHTML("![logo](https://example.com/logo.png)") - #expect(html.contains(#"href="mailto:hello@cleberg.net""#)) - #expect(html.contains(">hello@cleberg.net")) + #expect(html.contains("\"logo\"")) + #expect(!html.contains(#"\"#)) } @Test - func markdownListContinuesAfterCodeBlock() { - let html = markdownToHTML(""" - 1. Clone the repository: - ```sh - git clone https://git.sr.ht/~ccleberg/Hutch - ``` - 2. Open the project in Xcode. - """) + func markdownLinkedImageRendersAnchor() { + let html = markdownToHTML("[![badge](https://example.com/badge.png)](https://example.com/build)") - #expect(html.contains("
      ")) - #expect(html.contains("
      "))
      -        #expect(html.contains("
    1. Clone the repository:

      ")) - #expect(html.contains("
    2. Open the project in Xcode.
    3. ")) - #expect(html.contains("
    ")) + #expect(html.contains("")) + #expect(html.contains("\"badge\"")) + #expect(!html.contains(#"\"#)) } @Test - func markdownListContinuesAfterBlankLineIndentedCodeBlock() { - let html = markdownToHTML(""" - 1. Clone the repository: + func markdownInlineCodeEscaping() { + let html = processInline("``") - ```sh - git clone https://git.sr.ht/~ccleberg/Hutch - ``` + #expect(html.contains("")) + #expect(html.contains("<b>")) + #expect(!html.contains("")) + } - 2. Open the project in Xcode. - """) + @Test + func markdownPlainEmailAutolinks() { + let html = processInline("Contact hello@cleberg.net") - #expect(html.contains("
  • Clone the repository:

    ")) - #expect(html.contains("
    git clone https://git.sr.ht/~ccleberg/Hutch
    ")) - #expect(html.contains("
  • Open the project in Xcode.
  • ")) - #expect(!html.contains("
      \n
    1. Open the project in Xcode.
    2. \n
    \n
      ")) - #expect(html.firstRange(of: "

      Clone the repository:

      ")!.lowerBound < html.firstRange(of: "
      git clone https://git.sr.ht/~ccleberg/Hutch
      ")!.lowerBound) + #expect(html.contains(#"href="mailto:hello@cleberg.net""#)) + #expect(html.contains(">hello@cleberg.net
      ")) } @Test @@ -151,6 +136,20 @@ struct MarkdownRenderingTests { #expect(html.contains("Bold Link"#) -- cgit v1.2.3