| 1 | import Foundation |
| 2 | import XCTest |
| 3 | @testable import DocsKit |
| 4 | |
| 5 | final class DocumentCommandsTests: XCTestCase { |
| 6 | @MainActor func testTypingInheritsPreviousTextButDoesNotExtendLinksOrCrossParagraphs() throws { |
| 7 | let content = NSMutableAttributedString(string: "boldplain\nnext\n") |
| 8 | let strong: [String: JSONValue] = ["strong": .object([:])] |
| 9 | let link: [String: JSONValue] = ["link": .object(["href": .string("https://example.com")])] |
| 10 | content.addAttribute(.docsMarks, value: try JSONEncoder().encode(strong), range: NSRange(location: 0, length: 4)) |
| 11 | content.addAttribute(.docsMarks, value: try JSONEncoder().encode([String: JSONValue]()), range: NSRange(location: 4, length: 5)) |
| 12 | content.addAttribute(.docsMarks, value: try JSONEncoder().encode(link), range: NSRange(location: 10, length: 4)) |
| 13 | XCTAssertEqual(TextEditMapping.marks(in: content, at: 4), strong) |
| 14 | XCTAssertEqual(TextEditMapping.marks(in: content, at: 4, preferFollowing: true), [:]) |
| 15 | XCTAssertEqual(TextEditMapping.marks(in: content, at: 10), [:]) |
| 16 | XCTAssertEqual(TextEditMapping.marks(in: content, at: 12), link) |
| 17 | XCTAssertEqual(TextEditMapping.marks(in: content, at: 14), [:]) |
| 18 | let onlyBold = NSMutableAttributedString(string: "bold\n") |
| 19 | onlyBold.addAttribute(.docsMarks, value: try JSONEncoder().encode(strong), range: NSRange(location: 0, length: 4)) |
| 20 | XCTAssertEqual(TextEditMapping.displayCommand(in: onlyBold, range: NSRange(location: 4, length: 0), replacement: "!")?["marks"], .object(strong)) |
| 21 | } |
| 22 | @MainActor func testListConversionsTargetEachContainerOnceAndLiftFromEnd() { |
| 23 | let list = CRDTNode(id: "list", type: "bullet_list") |
| 24 | let first = CRDTNode(id: "first", type: "list_item") |
| 25 | let second = CRDTNode(id: "second", type: "list_item") |
| 26 | let paths = [ |
| 27 | [list, first, CRDTNode(id: "a", type: "paragraph")], |
| 28 | [list, first, CRDTNode(id: "b", type: "paragraph")], |
| 29 | [list, second, CRDTNode(id: "c", type: "paragraph")], |
| 30 | ] |
| 31 | let converted = DocumentCommands.listCommands(type: "ordered_list", paths: paths) |
| 32 | XCTAssertEqual(converted.count, 1) |
| 33 | XCTAssertEqual(converted.first?["node"], .string("c")) |
| 34 | let lifted = DocumentCommands.listCommands(type: "bullet_list", paths: paths) |
| 35 | XCTAssertEqual(lifted.map { $0["node"] }, [.string("c"), .string("b")]) |
| 36 | } |
| 37 | @MainActor func testFormattingAcrossBlocksKeepsUTF16OffsetsAndSkipsAttachments() throws { |
| 38 | let content = NSMutableAttributedString(string: "a😀b\ncd\n\u{fffc}") |
| 39 | let marks = try JSONEncoder().encode([String: JSONValue]()) |
| 40 | content.addAttributes([.docsNode: "first", .docsMarks: marks], range: NSRange(location: 0, length: 4)) |
| 41 | content.addAttributes([.docsNode: "second", .docsMarks: marks], range: NSRange(location: 5, length: 2)) |
| 42 | content.addAttribute(.docsNode, value: "image", range: NSRange(location: 8, length: 1)) |
| 43 | let commands = DocumentCommands.markCommands(content: content, selection: NSRange(location: 1, length: 8), marks: ["strong": .object([:])]) |
| 44 | XCTAssertEqual(commands.count, 2) |
| 45 | XCTAssertEqual(commands[0]["node"], .string("first")) |
| 46 | XCTAssertEqual(commands[0]["at"], .number(1)) |
| 47 | XCTAssertEqual(commands[0]["length"], .number(3)) |
| 48 | XCTAssertEqual(commands[1]["node"], .string("second")) |
| 49 | XCTAssertEqual(commands[1]["at"], .number(0)) |
| 50 | XCTAssertEqual(commands[1]["length"], .number(2)) |
| 51 | XCTAssertEqual(commands[1]["op"], .string("format")) |
| 52 | XCTAssertNil(commands[1]["text"]) |
| 53 | } |
| 54 | @MainActor func testExportNamesUseDocumentTitleWithoutPathOrOversizeBytes() { |
| 55 | XCTAssertEqual(DocumentExportControls.filename(title: "Quarterly report", extension: "pdf"), "Quarterly report.pdf") |
| 56 | XCTAssertEqual(DocumentExportControls.filename(title: "Notes.md", extension: "md"), "Notes.md") |
| 57 | let name = DocumentExportControls.filename(title: "../team:notes\\draft\n" + String(repeating: "日本語", count: 100), extension: "md") |
| 58 | XCTAssertFalse(name.contains("/")); XCTAssertFalse(name.contains("\\")); XCTAssertFalse(name.contains(":")); XCTAssertFalse(name.contains("\n")) |
| 59 | XCTAssertLessThanOrEqual(name.utf8.count, 183) |
| 60 | XCTAssertTrue(name.hasSuffix(".md")) |
| 61 | } |
| 62 | } |
| 63 | |