gitbayTests/MarkdownTests.swift

v1.9.0
gitbay-ios/gitbayTests/MarkdownTests.swift history · blame · raw

206 lines · 6727 bytes

  1import Foundation
  2import OrgSwift
  3import Testing
  4@testable import gitbay
  5
  6@MainActor
  7struct MarkdownTableTests {
  8
  9    private func blocks(_ markdown: String) -> [MarkdownView.Block] {
 10        MarkdownView(markdown: markdown).blocks
 11    }
 12
 13    @Test func headerSeparatorAndBodyBecomeATable() {
 14        let parsed = blocks("""
 15            | Add | Documented in |
 16            |---|---|
 17            | An RSS feed | [Collections](https://orgo.krz.sh/) |
 18            """)
 19        #expect(parsed == [.table(MarkdownView.Table(
 20            header: ["Add", "Documented in"],
 21            rows: [["An RSS feed", "[Collections](https://orgo.krz.sh/)"]],
 22            alignments: [.leading, .leading]
 23        ))])
 24    }
 25
 26    @Test func alignmentComesFromTheSeparatorRow() {
 27        let parsed = blocks("""
 28            | l | c | r |
 29            |:--|:-:|--:|
 30            | a | b | c |
 31            """)
 32        guard case .table(let table) = parsed.first else {
 33            Issue.record("expected a table, got \(parsed)")
 34            return
 35        }
 36        #expect(table.alignments == [.leading, .center, .trailing])
 37    }
 38
 39    @Test func outerPipesAreOptionalAndEscapedPipesStayInTheCell() {
 40        let parsed = blocks("""
 41            a | b
 42            --- | ---
 43            x \\| y | z
 44            """)
 45        guard case .table(let table) = parsed.first else {
 46            Issue.record("expected a table, got \(parsed)")
 47            return
 48        }
 49        #expect(table.header == ["a", "b"])
 50        #expect(table.rows == [["x \\| y", "z"]])
 51    }
 52
 53    @Test func raggedRowsArePaddedToTheHeaderWidth() {
 54        let parsed = blocks("""
 55            | a | b | c |
 56            |---|---|---|
 57            | 1 |
 58            | 1 | 2 | 3 | 4 |
 59            """)
 60        guard case .table(let table) = parsed.first else {
 61            Issue.record("expected a table, got \(parsed)")
 62            return
 63        }
 64        #expect(table.rows == [["1", "", ""], ["1", "2", "3"]])
 65    }
 66
 67    @Test func aTableEndsAtABlankLineAndTheTextAfterItIsAParagraph() {
 68        let parsed = blocks("""
 69            | a |
 70            |---|
 71            | 1 |
 72
 73            After the table.
 74            """)
 75        #expect(parsed.count == 2)
 76        #expect(parsed.last == .paragraph("After the table."))
 77    }
 78
 79    @Test func aTableInterruptsThePrecedingParagraph() {
 80        let parsed = blocks("""
 81            Each of these has a page in the guide:
 82            | a |
 83            |---|
 84            | 1 |
 85            """)
 86        #expect(parsed.first == .paragraph("Each of these has a page in the guide:"))
 87        #expect(parsed.count == 2)
 88    }
 89
 90    @Test func aPipeInProseIsNotATable() {
 91        let parsed = blocks("Run `a | b` to pipe.\nIt is not a table.")
 92        #expect(parsed == [.paragraph("Run `a | b` to pipe. It is not a table.")])
 93    }
 94}
 95
 96@MainActor
 97struct MarkdownLinkTests {
 98
 99    private let options = OrgRenderOptions(
100        host: "gitbay.org",
101        owner: "krz",
102        repositoryName: "org-swift",
103        ref: "HEAD",
104        readmePath: "README.md",
105        imagePathSegment: "raw",
106        linkPathSegment: "blob"
107    )
108
109    private func links(_ markdown: String, options: OrgRenderOptions?) -> [String] {
110        MarkdownView.attributed(markdown, linkOptions: options)
111            .runs.compactMap { $0.link?.absoluteString }
112    }
113
114    /// A relative target resolves to the repository's file page. Left alone it is a
115    /// scheme-less URL, which renders styled and does nothing when tapped.
116    @Test func relativeTargetsResolveAgainstTheRepository() {
117        #expect(links("See [ARCHITECTURE.md](ARCHITECTURE.md).", options: options)
118            == ["https://gitbay.org/krz/org-swift/blob/HEAD/ARCHITECTURE.md"])
119        #expect(links("[gaps](./GAPS.md)", options: options)
120            == ["https://gitbay.org/krz/org-swift/blob/HEAD/GAPS.md"])
121        #expect(links("[corpus](../org-conformance)", options: options)
122            == ["https://gitbay.org/krz/org-swift/blob/HEAD/org-conformance"])
123        #expect(links("[docs](/docs/x.md)", options: options)
124            == ["https://gitbay.org/krz/org-swift/blob/HEAD/docs/x.md"])
125    }
126
127    @Test func absoluteTargetsAreUntouched() {
128        #expect(links("[home](https://example.com/a)", options: options)
129            == ["https://example.com/a"])
130    }
131
132    /// A body with no file to be relative to keeps its targets as authored.
133    @Test func withoutOptionsTargetsAreLeftAlone() {
134        #expect(links("See [ARCHITECTURE.md](ARCHITECTURE.md).", options: nil)
135            == ["ARCHITECTURE.md"])
136    }
137}
138
139@MainActor
140struct MarkdownListTests {
141
142    private func blocks(_ markdown: String) -> [MarkdownView.Block] {
143        MarkdownView(markdown: markdown).blocks
144    }
145
146    @Test func indentedContinuationLinesStayInTheItem() {
147        let parsed = blocks("""
148            - Org source files in `content/`.
149              That directory is the URL root.
150            - HTML templates.
151            """)
152        #expect(parsed == [.bullet([
153            "Org source files in `content/`. That directory is the URL root.",
154            "HTML templates.",
155        ])])
156    }
157
158    @Test func unindentedContinuationLinesStayInTheItem() {
159        let parsed = blocks("- one\ncontinued\n- two")
160        #expect(parsed == [.bullet(["one continued", "two"])])
161    }
162
163    @Test func orderedItemsTakeContinuationLines() {
164        let parsed = blocks("1. first\n   more\n2. second")
165        #expect(parsed == [.ordered(["first more", "second"])])
166    }
167
168    @Test func aListEndsAtABlankLine() {
169        let parsed = blocks("- one\n\nAfter the list.")
170        #expect(parsed == [.bullet(["one"]), .paragraph("After the list.")])
171    }
172
173    @Test func aHeadingEndsAList() {
174        let parsed = blocks("- one\n## Next")
175        #expect(parsed == [.bullet(["one"]), .heading(level: 2, text: "Next")])
176    }
177}
178
179@MainActor
180struct MarkdownHeadingTests {
181
182    private func blocks(_ markdown: String) -> [MarkdownView.Block] {
183        MarkdownView(markdown: markdown).blocks
184    }
185
186    @Test func hashWithoutSpaceIsAParagraph() {
187        #expect(blocks("#12 fixed this") == [.paragraph("#12 fixed this")])
188    }
189
190    @Test func hashReferenceDoesNotEndAParagraph() {
191        #expect(blocks("see\n#12 for context") == [.paragraph("see #12 for context")])
192    }
193
194    @Test func hashReferenceContinuesAListItem() {
195        #expect(blocks("- fixed in\n#12") == blocks("- fixed in #12"))
196    }
197
198    @Test func headingNeedsSpaceOrEndOfLine() {
199        #expect(blocks("## Usage") == [.heading(level: 2, text: "Usage")])
200        #expect(blocks("#") == [.heading(level: 1, text: "")])
201    }
202
203    @Test func sevenHashesIsAParagraph() {
204        #expect(blocks("####### x") == [.paragraph("####### x")])
205    }
206}