2023-11-25 02:59:30 +00:00
|
|
|
//
|
|
|
|
// AttributedStringConverterTests.swift
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 11/24/23.
|
|
|
|
//
|
|
|
|
|
|
|
|
import XCTest
|
|
|
|
@testable import HTMLStreamer
|
|
|
|
|
|
|
|
final class AttributedStringConverterTests: XCTestCase {
|
|
|
|
|
|
|
|
#if os(iOS)
|
|
|
|
private let font = UIFont.systemFont(ofSize: 13)
|
2023-12-24 16:52:36 +00:00
|
|
|
private let color = UIColor.black
|
2023-12-17 23:35:07 +00:00
|
|
|
private lazy var italicFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits([.traitItalic])!, size: 13)
|
|
|
|
private lazy var boldFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits([.traitBold])!, size: 13)
|
|
|
|
private lazy var boldItalicFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic])!, size: 13)
|
2023-11-25 02:59:30 +00:00
|
|
|
private let monospaceFont = UIFont.monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
|
|
#elseif os(macOS)
|
|
|
|
private let font = NSFont.systemFont(ofSize: 13)
|
2023-12-24 16:52:36 +00:00
|
|
|
private let color = NSColor.black
|
2023-11-25 02:59:30 +00:00
|
|
|
private lazy var italicFont = NSFont(descriptor: font.fontDescriptor.withSymbolicTraits(.italic), size: 13)!
|
|
|
|
private lazy var boldFont = NSFont(descriptor: font.fontDescriptor.withSymbolicTraits(.bold), size: 13)!
|
|
|
|
private lazy var boldItalicFont = NSFont(descriptor: font.fontDescriptor.withSymbolicTraits([.bold, .italic]), size: 13)!
|
|
|
|
private let monospaceFont = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
|
|
#endif
|
|
|
|
private let blockquoteParagraphStyle: NSParagraphStyle = {
|
|
|
|
let style = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
|
|
|
style.headIndent = 32
|
|
|
|
style.firstLineHeadIndent = 32
|
|
|
|
return style
|
|
|
|
}()
|
2023-11-25 14:44:53 +00:00
|
|
|
private let listParagraphStyle: NSParagraphStyle = {
|
|
|
|
let style = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
|
|
|
style.headIndent = 32
|
|
|
|
style.firstLineHeadIndent = 0
|
|
|
|
style.tabStops = [NSTextTab(textAlignment: .right, location: 28), NSTextTab(textAlignment: .natural, location: 32)]
|
|
|
|
return style
|
|
|
|
}()
|
2023-11-25 02:59:30 +00:00
|
|
|
|
|
|
|
private func convert(_ html: String) -> NSAttributedString {
|
|
|
|
convert(html, callbacks: DefaultCallbacks.self)
|
|
|
|
}
|
|
|
|
|
2023-12-23 01:30:29 +00:00
|
|
|
private func convert<Callbacks: HTMLConversionCallbacks>(_ html: String, callbacks _: Callbacks.Type = Callbacks.self) -> NSAttributedString {
|
2023-11-25 02:59:30 +00:00
|
|
|
let config = AttributedStringConverterConfiguration(
|
|
|
|
font: font,
|
|
|
|
monospaceFont: monospaceFont,
|
2023-12-24 16:52:36 +00:00
|
|
|
color: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
paragraphStyle: .default
|
|
|
|
)
|
2024-02-28 22:55:48 +00:00
|
|
|
let converter = AttributedStringConverter<Callbacks>(configuration: config)
|
2023-11-26 23:53:59 +00:00
|
|
|
return converter.convert(html: html)
|
2023-11-25 02:59:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertBR() {
|
|
|
|
XCTAssertEqual(convert("a<br>b"), NSAttributedString(string: "a\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-12-22 23:43:53 +00:00
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("a<br />b"), NSAttributedString(string: "a\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertA() {
|
|
|
|
XCTAssertEqual(convert("<a href='https://example.com'>link</a>"), NSAttributedString(string: "link", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
.link: URL(string: "https://example.com")!,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<a>link</a>"), NSAttributedString(string: "link", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertP() {
|
|
|
|
XCTAssertEqual(convert("<p>a</p><p>b</p>"), NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertEm() {
|
|
|
|
XCTAssertEqual(convert("<em>hello</em>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<i>hello</i>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertStrong() {
|
|
|
|
XCTAssertEqual(convert("<strong>hello</strong>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: boldFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<b>hello</b>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: boldFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConvertBoldItalic() {
|
|
|
|
XCTAssertEqual(convert("<strong><em>hello</em></strong>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: boldItalicFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIncorrectNesting() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "bold ", attributes: [
|
|
|
|
.font: boldFont,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "both", attributes: [
|
|
|
|
.font: boldItalicFont,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: " italic", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
]))
|
|
|
|
result.addAttribute(.paragraphStyle, value: NSParagraphStyle.default, range: NSRange(location: 0, length: result.length))
|
2023-12-24 16:52:36 +00:00
|
|
|
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
|
2023-11-25 02:59:30 +00:00
|
|
|
XCTAssertEqual(convert("<strong>bold <em>both</strong> italic</em>"), result)
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:32:00 +00:00
|
|
|
func testMisnestedLink() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "hello ", attributes: [
|
|
|
|
.font: font,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "world", attributes: [
|
|
|
|
.font: boldFont,
|
|
|
|
]))
|
2023-12-24 16:52:36 +00:00
|
|
|
result.addAttribute(.link, value: URL(string: "https://example.com")!, range: NSRange(location: 0, length: result.length))
|
|
|
|
result.addAttribute(.paragraphStyle, value: NSParagraphStyle.default, range: NSRange(location: 0, length: result.length))
|
|
|
|
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
|
2023-12-17 23:32:00 +00:00
|
|
|
XCTAssertEqual(convert("<a href='https://example.com'>hello <b>world</a></b>"), result)
|
|
|
|
}
|
|
|
|
|
2023-11-25 02:59:30 +00:00
|
|
|
func testDel() {
|
|
|
|
XCTAssertEqual(convert("<del>blah</del>"), NSAttributedString(string: "blah", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
|
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCode() {
|
|
|
|
XCTAssertEqual(convert("<code>wee</code>"), NSAttributedString(string: "wee", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPre() {
|
|
|
|
XCTAssertEqual(convert("<pre>wee</pre>"), NSAttributedString(string: "wee", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testBlockquote() {
|
|
|
|
XCTAssertEqual(convert("<blockquote>hello</blockquote>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<blockquote><b>hello</b></blockquote>"), NSAttributedString(string: "hello", attributes: [
|
|
|
|
.font: boldItalicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
2023-12-17 23:54:26 +00:00
|
|
|
func testTextAfterBlockquote() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "wee", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "\n\nafter", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
]))
|
2023-12-24 16:52:36 +00:00
|
|
|
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
|
2023-12-17 23:54:26 +00:00
|
|
|
XCTAssertEqual(convert("<blockquote>wee</blockquote>after"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleBlockElements() {
|
2024-02-15 02:07:19 +00:00
|
|
|
let result = NSAttributedString(string: "a\n\nb", attributes: [
|
2023-12-17 23:54:26 +00:00
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
2024-02-15 02:07:19 +00:00
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
2023-12-17 23:54:26 +00:00
|
|
|
XCTAssertEqual(convert("<blockquote>a</blockquote><blockquote>b</blockquote>"), result)
|
|
|
|
}
|
|
|
|
|
2023-11-25 02:59:30 +00:00
|
|
|
func testSelfClosing() {
|
|
|
|
XCTAssertEqual(convert("<b />asdf"), NSAttributedString(string: "asdf", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMakeURLCallback() {
|
2023-12-23 01:30:29 +00:00
|
|
|
struct Callbacks: HTMLConversionCallbacks {
|
2023-11-25 02:59:30 +00:00
|
|
|
static func makeURL(string: String) -> URL? {
|
|
|
|
URL(string: "https://apple.com")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let result = convert("<a href='https://example.com'>test</a>", callbacks: Callbacks.self)
|
|
|
|
XCTAssertEqual(result, NSAttributedString(string: "test", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.link: URL(string: "https://apple.com")!,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
|
|
|
func testElementActionCallback() {
|
2023-12-23 01:30:29 +00:00
|
|
|
struct Callbacks: HTMLConversionCallbacks {
|
2023-11-27 03:35:10 +00:00
|
|
|
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
2023-11-25 02:59:30 +00:00
|
|
|
let clazz = attributes.attributeValue(for: "class")
|
2024-01-17 00:15:56 +00:00
|
|
|
if clazz == "skip" {
|
2023-11-25 02:59:30 +00:00
|
|
|
return .skip
|
2024-01-17 00:15:56 +00:00
|
|
|
} else if clazz == "replace" {
|
2023-11-25 02:59:30 +00:00
|
|
|
return .replace("…")
|
2024-01-17 00:15:56 +00:00
|
|
|
} else if clazz == "append" {
|
|
|
|
return .append("…")
|
2023-11-25 02:59:30 +00:00
|
|
|
} else {
|
|
|
|
return .default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-17 00:15:56 +00:00
|
|
|
let skipped = convert("<span class='skip'>test</span>", callbacks: Callbacks.self)
|
2023-11-25 02:59:30 +00:00
|
|
|
XCTAssertEqual(skipped, NSAttributedString())
|
2024-01-17 00:15:56 +00:00
|
|
|
let skipNested = convert("<span class='skip'><b>test</b></span>", callbacks: Callbacks.self)
|
2023-12-23 01:30:29 +00:00
|
|
|
XCTAssertEqual(skipNested, NSAttributedString())
|
2024-01-17 00:15:56 +00:00
|
|
|
let skipNestped2 = convert("<b><span class='skip'>test</span></b>", callbacks: Callbacks.self)
|
2023-11-25 02:59:30 +00:00
|
|
|
XCTAssertEqual(skipNestped2, NSAttributedString())
|
2024-01-17 00:15:56 +00:00
|
|
|
let replaced = convert("<span class='replace'>test</span>", callbacks: Callbacks.self)
|
2023-11-25 02:59:30 +00:00
|
|
|
XCTAssertEqual(replaced, NSAttributedString(string: "…", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 02:59:30 +00:00
|
|
|
]))
|
2024-03-17 14:56:49 +00:00
|
|
|
let replaceNested = convert("<span class='replace'><b>a</b></span>", callbacks: Callbacks.self)
|
|
|
|
XCTAssertEqual(replaceNested, NSAttributedString(string: "…", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
2024-01-17 00:15:56 +00:00
|
|
|
let appended = convert("<span class='append'>test</span>", callbacks: Callbacks.self)
|
|
|
|
XCTAssertEqual(appended, NSAttributedString(string: "test…", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
let appended2 = convert("<span class='append'>test <span>blah</span></span>", callbacks: Callbacks.self)
|
|
|
|
XCTAssertEqual(appended2, NSAttributedString(string: "test blah…", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
2023-11-25 02:59:30 +00:00
|
|
|
}
|
|
|
|
|
2023-11-25 14:44:53 +00:00
|
|
|
func testOrderedList() {
|
|
|
|
let result = convert("<ol><li>a</li><li>b</li></ol>")
|
|
|
|
XCTAssertEqual(result, NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-25 14:44:53 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
2023-11-28 16:56:56 +00:00
|
|
|
func testMultiScalar() {
|
|
|
|
XCTAssertEqual(convert("🇺🇸"), NSAttributedString(string: "🇺🇸", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
2023-12-24 16:52:36 +00:00
|
|
|
.foregroundColor: color,
|
2023-11-28 16:56:56 +00:00
|
|
|
]))
|
|
|
|
}
|
|
|
|
|
2023-12-26 19:29:40 +00:00
|
|
|
func testEmptyBlockElements() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "inside\nquote", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "\n\nafter", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<p></p><blockquote><span>inside<br>quote</span></blockquote><span>after</span><p></p>"), result)
|
|
|
|
}
|
|
|
|
|
2024-01-17 20:28:06 +00:00
|
|
|
func testFollowedByList() {
|
2024-01-17 00:26:40 +00:00
|
|
|
let result = NSMutableAttributedString()
|
2024-02-15 02:07:19 +00:00
|
|
|
result.append(NSAttributedString(string: "a", attributes: [
|
2024-01-17 00:26:40 +00:00
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
2024-02-15 02:07:19 +00:00
|
|
|
result.append(NSAttributedString(string: "\n\n\t1.\tb\n\t2.\tc", attributes: [
|
2024-01-17 00:26:40 +00:00
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<p>a</p><ol><li>b</li><li>c</li></ol>"), result)
|
2024-01-17 20:28:06 +00:00
|
|
|
XCTAssertEqual(convert("<span>a</span><ol><li>b</li><li>c</li></ol>"), result)
|
|
|
|
XCTAssertEqual(convert("a<ol><li>b</li><li>c</li></ol>"), result)
|
2024-01-17 00:26:40 +00:00
|
|
|
}
|
|
|
|
|
2024-02-21 16:15:27 +00:00
|
|
|
func testListItemOutsideList() {
|
|
|
|
let result = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<li>a</li>"), result)
|
|
|
|
}
|
|
|
|
|
2024-02-04 20:09:08 +00:00
|
|
|
func testSkipElementActionFollowingUnfinishedRun() {
|
|
|
|
struct Callbacks: HTMLConversionCallbacks {
|
|
|
|
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
|
|
|
attributes.attributeValue(for: "class") == "invisible" ? .skip : .default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "example.com", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
.link: URL(string: "https://example.com")!,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert(#"<a href="https://example.com"><span class="invisible">https://</span><span>example.com</span><span class="invisible"></span></a>"#, callbacks: Callbacks.self), result)
|
|
|
|
}
|
|
|
|
|
2024-02-06 23:53:47 +00:00
|
|
|
func testMalformedOnlyClosingTag() {
|
|
|
|
XCTAssertEqual(convert("</span>"), .init())
|
|
|
|
}
|
|
|
|
|
2024-02-21 16:15:27 +00:00
|
|
|
func testMultipleClosingBlockTagsBeforeOpeningBlockTag() {
|
2024-02-15 02:07:19 +00:00
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: italicFont,
|
|
|
|
.paragraphStyle: blockquoteParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert(#"<blockquote><p>a</p></blockquote><p>b</p>"#), result)
|
|
|
|
}
|
|
|
|
|
2024-02-21 16:15:27 +00:00
|
|
|
func testNewlineBetweenClosingAndOpeningBlockTag() {
|
|
|
|
let result = NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a</p>\n<p>b</p>"), result)
|
|
|
|
XCTAssertEqual(convert("<p>a</p><p>\nb</p>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEndAfterNewlineInBlockContent() {
|
|
|
|
let result = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a\n\n</p>"), result)
|
|
|
|
XCTAssertEqual(convert("<p>a\n\n</p>\n"), result)
|
|
|
|
XCTAssertEqual(convert("<p>\n\na</p>"), result)
|
|
|
|
XCTAssertEqual(convert("<p>\n\na</p>\n"), result)
|
2024-02-28 22:55:48 +00:00
|
|
|
let result2 = NSAttributedString(string: "a\n\n\nb", attributes: [
|
2024-02-21 16:15:27 +00:00
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a\n\n\nb</p>"), result2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testBRAtBlockElementBoundary() {
|
|
|
|
let two = NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a<br></p><p>b</p>"), two)
|
|
|
|
let three = NSAttributedString(string: "a\n\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a</p><p><br>b</p>"), three)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPreFollowedByP() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<pre>a<br></pre><p>b</p>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPreFollowedByPre() {
|
|
|
|
let result = NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a</pre><pre>b</pre>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testBRAtPreBoundary() {
|
|
|
|
let two = NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a<br></pre><pre>b</pre>"), two)
|
|
|
|
let three = NSAttributedString(string: "a\n\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a</pre><pre><br>b</pre>"), three)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testNestedPre() {
|
|
|
|
let one = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre><pre>a</pre></pre>"), one)
|
|
|
|
let two = NSAttributedString(string: "a\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a<pre>b</pre></pre>"), two)
|
|
|
|
XCTAssertEqual(convert("<pre>a<br><pre>b</pre></pre>"), two)
|
|
|
|
let three = NSAttributedString(string: "a\n\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a<pre><br>b</pre></pre>"), three)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoreLeadingNewlineInPre() {
|
|
|
|
let one = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>\na</pre>"), one)
|
|
|
|
let two = NSMutableAttributedString()
|
|
|
|
two.append(NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
two.append(NSAttributedString(string: "\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("a<pre>\nb</pre>"), two)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPreFollowingChar() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "\n\nb", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("a<pre>b</pre>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testSkipLeadingTrailingWhitespace() {
|
|
|
|
let result = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert(" \n\ta"), result)
|
|
|
|
XCTAssertEqual(convert(" \n\t<p>a</p>"), result)
|
2024-02-28 22:55:48 +00:00
|
|
|
XCTAssertEqual(convert("a\n\t"), result)
|
2024-02-21 16:15:27 +00:00
|
|
|
XCTAssertEqual(convert("<p>a</p> \n\t"), result)
|
2024-02-28 22:55:48 +00:00
|
|
|
let result2 = NSAttributedString(string: "a ", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("a \n\t"), result2)
|
2024-02-21 16:15:27 +00:00
|
|
|
|
|
|
|
let pre = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert(" \n\t<pre>a</pre>"), pre)
|
|
|
|
XCTAssertEqual(convert("<pre>a</pre> \n\t"), pre)
|
|
|
|
}
|
|
|
|
|
2024-02-28 22:55:48 +00:00
|
|
|
func testDoesNotCollapseWhitespace() {
|
|
|
|
let result = NSAttributedString(string: "a \t\nb", attributes: [
|
2024-02-21 16:15:27 +00:00
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a \t\nb</p>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testParagraphInsideListItem() {
|
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li><p>a</p></li><li><p>b</p></li></ol>"), result)
|
|
|
|
}
|
|
|
|
|
2024-05-22 22:59:24 +00:00
|
|
|
func testMultipleParagraphsInsideListItem() {
|
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li><p>a</p><p>b</p></li></ol>"), result)
|
|
|
|
}
|
|
|
|
|
2024-02-21 16:15:27 +00:00
|
|
|
func testBreakBetweenListItems() {
|
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li>a</li><br><li>b</li></ol>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testCharacterBetweenListItems() {
|
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\t\tc\n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li>a</li>c<li>b</li></ol>"), result)
|
2024-02-28 22:55:48 +00:00
|
|
|
let result2 = NSAttributedString(string: "\t1.\ta\n\t\tc \n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li>a</li>c <li>b</li></ol>"), result2)
|
2024-02-21 16:15:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func testWhitespaceCollapsingInTextBetweenListItems() {
|
2024-02-28 22:55:48 +00:00
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\t\tc d\n\t2.\tb", attributes: [
|
2024-02-21 16:15:27 +00:00
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li>a</li>c d<li>b</li></ol>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testImplicitlyClosedListItem() {
|
|
|
|
let result = NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<ol><li>a<li>b</ol>"), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPreInsideListItem() {
|
|
|
|
let result = NSMutableAttributedString()
|
|
|
|
result.append(NSAttributedString(string: "\t1.\t", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
result.append(NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: listParagraphStyle,
|
|
|
|
.foregroundColor: color,
|
|
|
|
]))
|
|
|
|
XCTAssertEqual(convert("<ol><li><pre>a</pre></li></ol>"), result)
|
|
|
|
}
|
|
|
|
|
2024-03-17 14:56:49 +00:00
|
|
|
func testInvisibleAtBeginningOfParagraphDoesNotPreventParagraphBreak() {
|
|
|
|
struct Invisible: HTMLConversionCallbacks {
|
|
|
|
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
|
|
|
if attributes.attributeValue(for: "class") == "invisible" {
|
|
|
|
.skip
|
|
|
|
} else {
|
|
|
|
.default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = NSAttributedString(string: "a\n\nc", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
let html = """
|
|
|
|
<p>a</p><p><span class="invisible">b</span><span class="ellipsis">c</span></p>
|
|
|
|
"""
|
|
|
|
XCTAssertEqual(convert(html, callbacks: Invisible.self), result)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testReplaceAtBeginningOfParagraphDoesNotPreventParagraphBreak() {
|
|
|
|
struct Replace: HTMLConversionCallbacks {
|
|
|
|
static func elementAction(name: String, attributes: [Attribute]) -> ElementAction {
|
|
|
|
if attributes.attributeValue(for: "class") == "replace" {
|
|
|
|
.replace("c")
|
|
|
|
} else {
|
|
|
|
.default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = NSAttributedString(string: "a\n\nc", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
let html = """
|
|
|
|
<p>a</p><p><span class="replace">b</span></p>
|
|
|
|
"""
|
|
|
|
XCTAssertEqual(convert(html, callbacks: Replace.self), result)
|
|
|
|
}
|
|
|
|
|
2024-05-22 21:48:29 +00:00
|
|
|
func testLineBreakAtBeginningOfBlockElement() {
|
|
|
|
let result = NSAttributedString(string: "a\n\n\n\nb", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a</p><p><br></p><p>b</p>"), result)
|
|
|
|
|
|
|
|
let result2 = NSAttributedString(string: "a\n\n\nb\n\nc", attributes: [
|
|
|
|
.font: font,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<p>a</p><p><br>b</p><p>c</p>"), result2)
|
|
|
|
}
|
|
|
|
|
2024-07-09 04:12:44 +00:00
|
|
|
func testWhitespaceAtEndOfPre() {
|
|
|
|
let result = NSAttributedString(string: "a", attributes: [
|
|
|
|
.font: monospaceFont,
|
|
|
|
.paragraphStyle: NSParagraphStyle.default,
|
|
|
|
.foregroundColor: color,
|
|
|
|
])
|
|
|
|
XCTAssertEqual(convert("<pre>a </pre>"), result)
|
|
|
|
}
|
2024-05-22 21:48:29 +00:00
|
|
|
|
2023-11-25 02:59:30 +00:00
|
|
|
}
|