HTMLStreamer/Tests/HTMLStreamerTests/AttributedStringConverterTe...

309 lines
13 KiB
Swift

//
// 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 let color = UIColor.black
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 let color = NSColor.black
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<Callbacks: HTMLConversionCallbacks>(_ html: String, callbacks _: Callbacks.Type = Callbacks.self) -> NSAttributedString {
let config = AttributedStringConverterConfiguration(
font: font,
monospaceFont: monospaceFont,
color: color,
paragraphStyle: .default
)
var converter = AttributedStringConverter<Callbacks>(configuration: config)
return converter.convert(html: html)
}
func testConvertBR() {
XCTAssertEqual(convert("a<br>b"), NSAttributedString(string: "a\nb", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
XCTAssertEqual(convert("a<br />b"), NSAttributedString(string: "a\nb", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testConvertA() {
XCTAssertEqual(convert("<a href='https://example.com'>link</a>"), NSAttributedString(string: "link", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
.link: URL(string: "https://example.com")!,
]))
XCTAssertEqual(convert("<a>link</a>"), NSAttributedString(string: "link", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testConvertP() {
XCTAssertEqual(convert("<p>a</p><p>b</p>"), NSAttributedString(string: "a\n\nb", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testConvertEm() {
XCTAssertEqual(convert("<em>hello</em>"), NSAttributedString(string: "hello", attributes: [
.font: italicFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
XCTAssertEqual(convert("<i>hello</i>"), NSAttributedString(string: "hello", attributes: [
.font: italicFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testConvertStrong() {
XCTAssertEqual(convert("<strong>hello</strong>"), NSAttributedString(string: "hello", attributes: [
.font: boldFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
XCTAssertEqual(convert("<b>hello</b>"), NSAttributedString(string: "hello", attributes: [
.font: boldFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testConvertBoldItalic() {
XCTAssertEqual(convert("<strong><em>hello</em></strong>"), NSAttributedString(string: "hello", attributes: [
.font: boldItalicFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
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))
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
XCTAssertEqual(convert("<strong>bold <em>both</strong> italic</em>"), result)
}
func testMisnestedLink() {
let result = NSMutableAttributedString()
result.append(NSAttributedString(string: "hello ", attributes: [
.font: font,
]))
result.append(NSAttributedString(string: "world", attributes: [
.font: boldFont,
]))
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))
XCTAssertEqual(convert("<a href='https://example.com'>hello <b>world</a></b>"), result)
}
func testDel() {
XCTAssertEqual(convert("<del>blah</del>"), NSAttributedString(string: "blah", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
]))
}
func testCode() {
XCTAssertEqual(convert("<code>wee</code>"), NSAttributedString(string: "wee", attributes: [
.font: monospaceFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testPre() {
XCTAssertEqual(convert("<pre>wee</pre>"), NSAttributedString(string: "wee", attributes: [
.font: monospaceFont,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testBlockquote() {
XCTAssertEqual(convert("<blockquote>hello</blockquote>"), NSAttributedString(string: "hello", attributes: [
.font: italicFont,
.paragraphStyle: blockquoteParagraphStyle,
.foregroundColor: color,
]))
XCTAssertEqual(convert("<blockquote><b>hello</b></blockquote>"), NSAttributedString(string: "hello", attributes: [
.font: boldItalicFont,
.paragraphStyle: blockquoteParagraphStyle,
.foregroundColor: color,
]))
}
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,
]))
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
XCTAssertEqual(convert("<blockquote>wee</blockquote>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,
]))
result.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: result.length))
XCTAssertEqual(convert("<blockquote>a</blockquote><blockquote>b</blockquote>"), result)
}
func testSelfClosing() {
XCTAssertEqual(convert("<b />asdf"), NSAttributedString(string: "asdf", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
func testMakeURLCallback() {
struct Callbacks: HTMLConversionCallbacks {
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")!,
.foregroundColor: color,
]))
}
func testElementActionCallback() {
struct Callbacks: HTMLConversionCallbacks {
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("<span class='invisible'>test</span>", callbacks: Callbacks.self)
XCTAssertEqual(skipped, NSAttributedString())
let skipNested = convert("<span class='invisible'><b>test</b></span>", callbacks: Callbacks.self)
XCTAssertEqual(skipNested, NSAttributedString())
let skipNestped2 = convert("<b><span class='invisible'>test</span></b>", callbacks: Callbacks.self)
XCTAssertEqual(skipNestped2, NSAttributedString())
let replaced = convert("<span class='ellipsis'>test</span>", callbacks: Callbacks.self)
XCTAssertEqual(replaced, NSAttributedString(string: "", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
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,
.foregroundColor: color,
]))
}
func testMultiScalar() {
XCTAssertEqual(convert("🇺🇸"), NSAttributedString(string: "🇺🇸", attributes: [
.font: font,
.paragraphStyle: NSParagraphStyle.default,
.foregroundColor: color,
]))
}
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)
}
}