// // 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) 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) private let monospaceFont = UIFont.monospacedSystemFont(ofSize: 13, weight: .regular) #elseif os(macOS) private let font = NSFont.systemFont(ofSize: 13) 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 }() 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 }() private func convert(_ html: String) -> NSAttributedString { convert(html, callbacks: DefaultCallbacks.self) } private func convert(_ html: String, callbacks _: Callbacks.Type = Callbacks.self) -> NSAttributedString { let config = AttributedStringConverterConfiguration( font: font, monospaceFont: monospaceFont, color: .black, paragraphStyle: .default ) var converter = AttributedStringConverter(configuration: config) return converter.convert(html: html) } func testConvertBR() { XCTAssertEqual(convert("a
b"), NSAttributedString(string: "a\nb", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) XCTAssertEqual(convert("a
b"), NSAttributedString(string: "a\nb", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } func testConvertA() { XCTAssertEqual(convert("link"), NSAttributedString(string: "link", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, .link: URL(string: "https://example.com")!, ])) XCTAssertEqual(convert("link"), NSAttributedString(string: "link", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } func testConvertP() { XCTAssertEqual(convert("

a

b

"), NSAttributedString(string: "a\n\nb", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } func testConvertEm() { XCTAssertEqual(convert("hello"), NSAttributedString(string: "hello", attributes: [ .font: italicFont, .paragraphStyle: NSParagraphStyle.default, ])) XCTAssertEqual(convert("hello"), NSAttributedString(string: "hello", attributes: [ .font: italicFont, .paragraphStyle: NSParagraphStyle.default, ])) } func testConvertStrong() { XCTAssertEqual(convert("hello"), NSAttributedString(string: "hello", attributes: [ .font: boldFont, .paragraphStyle: NSParagraphStyle.default, ])) XCTAssertEqual(convert("hello"), NSAttributedString(string: "hello", attributes: [ .font: boldFont, .paragraphStyle: NSParagraphStyle.default, ])) } func testConvertBoldItalic() { XCTAssertEqual(convert("hello"), NSAttributedString(string: "hello", attributes: [ .font: boldItalicFont, .paragraphStyle: NSParagraphStyle.default, ])) } 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)) XCTAssertEqual(convert("bold both italic"), result) } func testMisnestedLink() { let result = NSMutableAttributedString() result.append(NSAttributedString(string: "hello ", attributes: [ .link: URL(string: "https://example.com")!, .font: font, .paragraphStyle: NSParagraphStyle.default, ])) result.append(NSAttributedString(string: "world", attributes: [ .link: URL(string: "https://example.com")!, .font: boldFont, .paragraphStyle: NSParagraphStyle.default, ])) XCTAssertEqual(convert("hello world"), result) } func testDel() { XCTAssertEqual(convert("blah"), NSAttributedString(string: "blah", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, .strikethroughStyle: NSUnderlineStyle.single.rawValue, ])) } func testCode() { XCTAssertEqual(convert("wee"), NSAttributedString(string: "wee", attributes: [ .font: monospaceFont, .paragraphStyle: NSParagraphStyle.default, ])) } func testPre() { XCTAssertEqual(convert("
wee
"), NSAttributedString(string: "wee", attributes: [ .font: monospaceFont, .paragraphStyle: NSParagraphStyle.default, ])) } func testBlockquote() { XCTAssertEqual(convert("
hello
"), NSAttributedString(string: "hello", attributes: [ .font: italicFont, .paragraphStyle: blockquoteParagraphStyle, ])) XCTAssertEqual(convert("
hello
"), NSAttributedString(string: "hello", attributes: [ .font: boldItalicFont, .paragraphStyle: blockquoteParagraphStyle, ])) } 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, ])) XCTAssertEqual(convert("
wee
after"), result) } func testMultipleBlockElements() { let result = NSMutableAttributedString() result.append(NSAttributedString(string: "a", attributes: [ .font: italicFont, .paragraphStyle: blockquoteParagraphStyle, ])) result.append(NSAttributedString(string: "\n\n", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) result.append(NSAttributedString(string: "b", attributes: [ .font: italicFont, .paragraphStyle: blockquoteParagraphStyle, ])) XCTAssertEqual(convert("
a
b
"), result) } func testSelfClosing() { XCTAssertEqual(convert("asdf"), NSAttributedString(string: "asdf", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } func testMakeURLCallback() { struct Callbacks: AttributedStringCallbacks { static func makeURL(string: String) -> URL? { URL(string: "https://apple.com") } } let result = convert("test", callbacks: Callbacks.self) XCTAssertEqual(result, NSAttributedString(string: "test", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, .link: URL(string: "https://apple.com")!, ])) } func testElementActionCallback() { struct Callbacks: AttributedStringCallbacks { static func elementAction(name: String, attributes: [Attribute]) -> ElementAction { let clazz = attributes.attributeValue(for: "class") if clazz == "invisible" { return .skip } else if clazz == "ellipsis" { return .replace("…") } else { return .default } } } let skipped = convert("", callbacks: Callbacks.self) XCTAssertEqual(skipped, NSAttributedString()) let skipNestped = convert("", callbacks: Callbacks.self) XCTAssertEqual(skipNestped, NSAttributedString()) let skipNestped2 = convert("", callbacks: Callbacks.self) XCTAssertEqual(skipNestped2, NSAttributedString()) let replaced = convert("test", callbacks: Callbacks.self) XCTAssertEqual(replaced, NSAttributedString(string: "…", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } func testOrderedList() { let result = convert("
  1. a
  2. b
") XCTAssertEqual(result, NSAttributedString(string: "\t1.\ta\n\t2.\tb", attributes: [ .font: font, .paragraphStyle: listParagraphStyle, ])) } func testMultiScalar() { XCTAssertEqual(convert("🇺🇸"), NSAttributedString(string: "🇺🇸", attributes: [ .font: font, .paragraphStyle: NSParagraphStyle.default, ])) } }