227 lines
9.7 KiB
Swift
227 lines
9.7 KiB
Swift
//
|
|
// GeminiParserTests.swift
|
|
// GeminiFormatTests
|
|
//
|
|
// Created by Shadowfacts on 7/12/20.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import GeminiFormat
|
|
|
|
class GeminiParserTests: XCTestCase {
|
|
|
|
func assertParseLines(text: String, lines expected: [Document.Line], message: String = "", file: StaticString = #filePath, line: UInt = #line) {
|
|
let doc = GeminiParser.parse(text: text, baseURL: URL(string: "gemini://example.com")!)
|
|
for (index, (actual, expected)) in zip(doc.lines, expected).enumerated() {
|
|
XCTAssertEqual(actual, expected, "\(message): index \(index)", file: file, line: line)
|
|
}
|
|
}
|
|
|
|
func testParsePlainLines() {
|
|
assertParseLines(text: "test", lines: [.text("test")], message: "parse a plain text line")
|
|
assertParseLines(text: "one\ntwo", lines: [
|
|
.text("one"),
|
|
.text("two")
|
|
], message: "parse multiple, newline delmited plain lines")
|
|
assertParseLines(text: "one\r\ntwo", lines: [
|
|
.text("one"),
|
|
.text("two")
|
|
], message: "parse multiple, CRLF delmited plain lines")
|
|
}
|
|
|
|
func testParseLinkLines() {
|
|
assertParseLines(text: "=> gemini://blah.com", lines: [
|
|
.link(URL(string: "gemini://blah.com")!, text: nil)
|
|
], message: "parse a bare link line")
|
|
assertParseLines(text: "=> gemini://blah.com:1234/foo/bar?baz", lines: [
|
|
.link(URL(string: "gemini://blah.com:1234/foo/bar?baz")!, text: nil)
|
|
], message: "parse a more complex bare link line")
|
|
assertParseLines(text: "=> gemini://blah.com \t Link to example", lines: [
|
|
.link(URL(string: "gemini://blah.com")!, text: "Link to example")
|
|
], message: "parse a simple link line with associated text")
|
|
assertParseLines(text: "=> gemini://blah.com/foo Link to foo", lines: [
|
|
.link(URL(string: "gemini://blah.com/foo")!, text: "Link to foo")
|
|
], message: "parse a more complex link line with associated text")
|
|
assertParseLines(text: "=> https://example.com", lines: [
|
|
.link(URL(string: "https://example.com")!, text: nil)
|
|
], message: "parse a link with a different protocol")
|
|
assertParseLines(text: "=> /foo/bar/baz", lines: [
|
|
.link(URL(string: "gemini://example.com/foo/bar/baz")!, text: nil)
|
|
], message: "resolve a relative path link")
|
|
assertParseLines(text: "=>gemini://blah.com", lines: [
|
|
.link(URL(string: "gemini://blah.com")!, text: nil)
|
|
], message: "parse link without whitespace after =>")
|
|
}
|
|
|
|
func testParseHeadingLines() {
|
|
assertParseLines(text: "# test", lines: [
|
|
.heading("test", level: .h1)
|
|
], message: "level 1 heading")
|
|
assertParseLines(text: "#test", lines: [
|
|
.heading("test", level: .h1)
|
|
], message: "level 1 heading without whitespace")
|
|
assertParseLines(text: "#test\n## two\r\n###three", lines: [
|
|
.heading("test", level: .h1),
|
|
.heading("two", level: .h2),
|
|
.heading("three", level: .h3)
|
|
], message: "multiples headings with and without whitespace")
|
|
}
|
|
|
|
func testParseListItemLines() {
|
|
assertParseLines(text: "* test list item", lines: [
|
|
.unorderedListItem("test list item")
|
|
], message: "parse simple list item")
|
|
assertParseLines(text: "*test", lines: [
|
|
.text("*test")
|
|
], message: "don't parse list item without space after asterisk")
|
|
assertParseLines(text: "* one\n* two\n*three", lines: [
|
|
.unorderedListItem("one"),
|
|
.unorderedListItem("two"),
|
|
.text("*three")
|
|
], message: "parse multiple list items")
|
|
}
|
|
|
|
func testParseQuoteLines() {
|
|
assertParseLines(text: "> quote", lines: [
|
|
.quote("quote")
|
|
], message: "parse quote line")
|
|
assertParseLines(text: ">quote", lines: [
|
|
.quote("quote")
|
|
], message: "parse quote line without space after >")
|
|
assertParseLines(text: ">one\n> two\n>three", lines: [
|
|
.quote("one"),
|
|
.quote("two"),
|
|
.quote("three")
|
|
], message: "parse multiple quote lines")
|
|
}
|
|
|
|
func testParsePreformattedLines() {
|
|
assertParseLines(text: "```\nsomething\n```", lines: [
|
|
.preformattedToggle(alt: nil),
|
|
.preformattedText("something"),
|
|
.preformattedToggle(alt: nil)
|
|
], message: "parse simple preformatted line")
|
|
assertParseLines(text: "```alt\nsomething\n```", lines: [
|
|
.preformattedToggle(alt: "alt"),
|
|
.preformattedText("something"),
|
|
.preformattedToggle(alt: nil)
|
|
], message: "parse simple preformatted line with alt")
|
|
assertParseLines(text: "```alt\nsomething\n```other", lines: [
|
|
.preformattedToggle(alt: "alt"),
|
|
.preformattedText("something"),
|
|
.preformattedToggle(alt: nil)
|
|
], message: "ignore extra text after closing ```")
|
|
assertParseLines(text: "```\n# not a heading\n* not a list item\n>not a quote\n=> /link not a link\n```", lines: [
|
|
.preformattedToggle(alt: nil),
|
|
.preformattedText("# not a heading"),
|
|
.preformattedText("* not a list item"),
|
|
.preformattedText(">not a quote"),
|
|
.preformattedText("=> /link not a link"),
|
|
.preformattedToggle(alt: nil)
|
|
], message: "don't parse special lines inside preformatted")
|
|
assertParseLines(text: "```a\na line\n```\n```b\nb line\n```", lines: [
|
|
.preformattedToggle(alt: "a"),
|
|
.preformattedText("a line"),
|
|
.preformattedToggle(alt: nil),
|
|
.preformattedToggle(alt: "b"),
|
|
.preformattedText("b line"),
|
|
.preformattedToggle(alt: nil),
|
|
], message: "parse consecutive preformatted blocks")
|
|
}
|
|
|
|
func testComplexDocument() {
|
|
let text = """
|
|
# Project Gemini
|
|
|
|
## Overview
|
|
|
|
Gemini is a new internet protocol which:
|
|
|
|
* Is heavier than gopher
|
|
* Is lighter than the web
|
|
* Will not replace either
|
|
* Strives for maximum power to weight ratio
|
|
* Takes user privacy very seriously
|
|
|
|
## Resources
|
|
|
|
=> docs/ Gemini documentation
|
|
=> software/ Gemini software
|
|
=> servers/ Known Gemini servers
|
|
=> https://lists.orbitalfox.eu/listinfo/gemini Gemini mailing list
|
|
=> gemini://gemini.conman.org/test/torture/ Gemini client torture test
|
|
|
|
## Web proxies
|
|
|
|
=> https://portal.mozz.us/?url=gemini%3A%2F%2Fgemini.circumlunar.space%2F&fmt=fixed Gemini-to-web proxy service
|
|
=> https://proxy.vulpes.one/gemini/gemini.circumlunar.space Another Gemini-to-web proxy service
|
|
|
|
## Search engines
|
|
|
|
=> gemini://gus.guru/ Gemini Universal Search engine
|
|
=> gemini://houston.coder.town Houston search engine
|
|
|
|
## Geminispace aggregators (experimental!)
|
|
|
|
=> capcom/ CAPCOM
|
|
=> gemini://rawtext.club:1965/~sloum/spacewalk.gmi Spacewalk
|
|
|
|
## Gemini mirrors of web resources
|
|
|
|
=> gemini://gempaper.strangled.net/mirrorlist/ A list of mirrored services
|
|
|
|
## Free Gemini hosting
|
|
|
|
=> users/ Users with Gemini content on this server
|
|
"""
|
|
|
|
let expected: [Document.Line] = [
|
|
.heading("Project Gemini", level: .h1),
|
|
.text(""),
|
|
.heading("Overview", level: .h2),
|
|
.text(""),
|
|
.text("Gemini is a new internet protocol which:"),
|
|
.text(""),
|
|
.unorderedListItem("Is heavier than gopher"),
|
|
.unorderedListItem("Is lighter than the web"),
|
|
.unorderedListItem("Will not replace either"),
|
|
.unorderedListItem("Strives for maximum power to weight ratio"),
|
|
.unorderedListItem("Takes user privacy very seriously"),
|
|
.text(""),
|
|
.heading("Resources", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "gemini://example.com/docs/")!, text: "Gemini documentation"),
|
|
.link(URL(string: "gemini://example.com/software/")!, text: "Gemini software"),
|
|
.link(URL(string: "gemini://example.com/servers/")!, text: "Known Gemini servers"),
|
|
.link(URL(string: "https://lists.orbitalfox.eu/listinfo/gemini")!, text: "Gemini mailing list"),
|
|
.link(URL(string: "gemini://gemini.conman.org/test/torture/")!, text: "Gemini client torture test"),
|
|
.text(""),
|
|
.heading("Web proxies", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "https://portal.mozz.us/?url=gemini%3A%2F%2Fgemini.circumlunar.space%2F&fmt=fixed")!, text: "Gemini-to-web proxy service"),
|
|
.link(URL(string: "https://proxy.vulpes.one/gemini/gemini.circumlunar.space")!, text: "Another Gemini-to-web proxy service"),
|
|
.text(""),
|
|
.heading("Search engines", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "gemini://gus.guru/")!, text: "Gemini Universal Search engine"),
|
|
.link(URL(string: "gemini://houston.coder.town")!, text: "Houston search engine"),
|
|
.text(""),
|
|
.heading("Geminispace aggregators (experimental!)", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "gemini://example.com/capcom/")!, text: "CAPCOM"),
|
|
.link(URL(string: "gemini://rawtext.club:1965/~sloum/spacewalk.gmi")!, text: "Spacewalk"),
|
|
.text(""),
|
|
.heading("Gemini mirrors of web resources", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "gemini://gempaper.strangled.net/mirrorlist/")!, text: "A list of mirrored services"),
|
|
.text(""),
|
|
.heading("Free Gemini hosting", level: .h2),
|
|
.text(""),
|
|
.link(URL(string: "gemini://example.com/users/")!, text: "Users with Gemini content on this server")
|
|
]
|
|
|
|
assertParseLines(text: text, lines: expected)
|
|
}
|
|
|
|
}
|