import AppKit import ConjureKit import Testing @Test @MainActor func markdownImportProducesVisibleTextAndSemanticFormatting() throws { let markdown = "# Title 🦊\n**bold** *italic* ***both*** under ~~gone~~ `let x = 1` [site](https://example.com)\n\n> quote\n\n- [ ] todo\n- [x] done\n - nested\n\n3. third\n4. fourth\n\n```swift\n# literal **code**\n```\n" let document = NoteDocument.importMarkdown(NSAttributedString(string: markdown)) #expect(document.string == "Title 🦊\nbold italic both under gone let x = 1 site\n\nquote\n\ntodo\ndone\nnested\n\nthird\nfourth\n\n# literal **code**\n") func attribute(_ key: NSAttributedString.Key, _ text: String) throws -> Any? { let range = (document.string as NSString).range(of: text) #expect(range.location != NSNotFound) return document.attribute(key, at: range.location, effectiveRange: nil) } #expect(try attribute(NoteAttributes.block, "Title") as? String == "heading") #expect(try attribute(NoteAttributes.headingLevel, "Title") as? Int == 1) #expect(try attribute(NoteAttributes.bold, "bold") as? Bool == true) #expect(try attribute(NoteAttributes.italic, "italic") as? Bool == true) #expect(try attribute(NoteAttributes.bold, "both") as? Bool == true) #expect(try attribute(NoteAttributes.italic, "both") as? Bool == true) #expect(try attribute(.underlineStyle, "under") as? Int == NSUnderlineStyle.single.rawValue) #expect(try attribute(.strikethroughStyle, "gone") as? Int == NSUnderlineStyle.single.rawValue) #expect(try attribute(NoteAttributes.inlineCode, "let x") as? Bool == true) #expect(try attribute(.link, "site") as? String == "https://example.com") #expect(try attribute(NoteAttributes.block, "quote") as? String == "quote") #expect(try attribute(NoteAttributes.checked, "todo") as? Bool == false) #expect(try attribute(NoteAttributes.checked, "done") as? Bool == true) #expect(try attribute(NoteAttributes.depth, "nested") as? Int == 1) #expect(try attribute(NoteAttributes.number, "third") as? Int == 3) #expect(try attribute(NoteAttributes.number, "fourth") as? Int == 4) #expect(try attribute(NoteAttributes.block, "# literal") as? String == "code") #expect(try attribute(NoteAttributes.language, "# literal") as? String == "swift") #expect(try attribute(NoteAttributes.bold, "**code**") == nil) let restored = NoteDocument.importMarkdown(NoteDocument.markdown(document)) #expect(restored.string == document.string) } @Test @MainActor func markdownImportHandlesEveryHeadingEscapesAndUnsupportedLiteralContent() { for level in 1...6 { let document = NoteDocument.importMarkdown(NSAttributedString(string: String(repeating: "#", count: level) + " Heading")) #expect(document.string == "Heading") #expect(document.attribute(NoteAttributes.headingLevel, at: 0, effectiveRange: nil) as? Int == level) } let escaped = NoteDocument.importMarkdown(NSAttributedString(string: "\\*literal\\* & café 🦊")) #expect(escaped.string == "*literal* & café 🦊") #expect(escaped.attribute(NoteAttributes.italic, at: 0, effectiveRange: nil) == nil) for literal in ["![remote](https://example.com/image.png)", "
literal **content**
", "| A | B |\n| - | - |\n| x | y |", "---"] { #expect(NoteDocument.importMarkdown(NSAttributedString(string: literal)).string == literal) } } @Test @MainActor func markdownConversionPreservesNativeAttachmentsAndLiteralCode() throws { let attachment = NSTextAttachment() let source = NSMutableAttributedString(string: "**fox ") source.append(NSAttributedString(attachment: attachment)) source.append(NSAttributedString(string: "**\n```text\n`literal` **not bold**\n```")) let document = NoteDocument.importMarkdown(source) #expect(document.string == "fox \u{fffc}\n`literal` **not bold**") #expect(document.attribute(.attachment, at: 4, effectiveRange: nil) as? NSTextAttachment === attachment) let markdown = NoteDocument.markdown(document) #expect(markdown.string.contains("```text\n`literal` **not bold**\n```")) let location = (markdown.string as NSString).range(of: "\u{fffc}").location #expect(markdown.attribute(.attachment, at: location, effectiveRange: nil) as? NSTextAttachment === attachment) } @Test @MainActor func markdownExportRecognizesNativeMarksAndEscapesLiteralSyntax() { let native = NSMutableAttributedString(string: "bold italic under strike link literal *stars* #tag") let text = native.string as NSString native.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: 14), range: text.range(of: "bold")) native.addAttribute(.font, value: NSFontManager.shared.convert(NSFont.systemFont(ofSize: 14), toHaveTrait: .italicFontMask), range: text.range(of: "italic")) native.addAttribute(.underlineStyle, value: 1, range: text.range(of: "under")) native.addAttribute(.strikethroughStyle, value: 1, range: text.range(of: "strike")) native.addAttribute(.link, value: URL(string: "https://example.com")!, range: text.range(of: "link")) let output = NoteDocument.markdown(native) #expect(output.string == "**bold** *italic* under ~~strike~~ [link]() literal \\*stars\\* \\#tag") #expect(NoteDocument.importMarkdown(output).string == native.string) native.addAttribute(NoteAttributes.bold, value: false, range: text.range(of: "bold")) #expect(NoteDocument.markdown(native).string.hasPrefix("bold ")) } @Test @MainActor func structuredPackageRoundTripsAllSemanticAttributesIncludingFalseAndZero() throws { let document = NSMutableAttributedString(attributedString: NoteDocument.importMarkdown(NSAttributedString(string: "###### Heading\n- [ ] todo\n 0. nested\n```swift\nlet x = 1\n```\n**bold** *italic* under ~~strike~~ `code` [link](https://example.com)"))) document.addAttributes([NoteAttributes.bold: false, NoteAttributes.italic: false, NoteAttributes.inlineCode: false, NoteAttributes.depth: 0], range: NSRange(location: 0, length: 1)) let wrapper = try NoteDocument.fileWrapper(document) let serialized = try #require(wrapper.serializedRepresentation) let received = try #require(FileWrapper(serializedRepresentation: serialized)) let restored = try NoteDocument.contents(from: received) #expect(restored.string == document.string) let keys: [NSAttributedString.Key] = [NoteAttributes.block, NoteAttributes.headingLevel, NoteAttributes.depth, NoteAttributes.number, NoteAttributes.checked, NoteAttributes.language, NoteAttributes.inlineCode, NoteAttributes.bold, NoteAttributes.italic, .underlineStyle, .strikethroughStyle, .link] for index in 0.. quote\n[link](https://example.com)\nnext", "quote\nlink\nnext"), ("- item\n***both*** under\nlast", "item\nboth under\nlast"), ] { let contents = NoteDocument.importMarkdown(NSAttributedString(string: source)) #expect(contents.string == expected) } let attachment = NSTextAttachment() let source = NSMutableAttributedString(string: "# Heading\n- [ ] task\n**bold** and `code`\n") source.append(NSAttributedString(attachment: attachment)) let contents = NoteDocument.importMarkdown(source) #expect(contents.string == "Heading\ntask\nbold and code\n\u{fffc}") #expect(contents.attribute(.attachment, at: contents.length - 1, effectiveRange: nil) as? NSTextAttachment === attachment) let bold = (contents.string as NSString).range(of: "bold") #expect(contents.attribute(NoteAttributes.bold, at: bold.location, effectiveRange: nil) as? Bool == true) let code = (contents.string as NSString).range(of: "code") #expect(contents.attribute(NoteAttributes.inlineCode, at: code.location, effectiveRange: nil) as? Bool == true) } @Test @MainActor func markdownExportGroupsSemanticMarksAcrossDisplayRunsAndKeepsWhitespace() { let contents = NSMutableAttributedString(string: "fox 🦊 \n") let marked = NSRange(location: 0, length: (contents.string as NSString).range(of: " \n").location) contents.addAttributes([.underlineStyle: 1, NoteAttributes.bold: false, NoteAttributes.italic: false], range: NSRange(location: 0, length: contents.length)) contents.addAttribute(.font, value: NSFont.systemFont(ofSize: 14), range: NSRange(location: 0, length: 4)) contents.addAttributes([.font: NSFont.systemFont(ofSize: 18), .foregroundColor: NSColor.red], range: NSRange(location: 4, length: marked.length - 4)) #expect(NoteDocument.markdown(contents).string == "fox 🦊 \n") contents.addAttribute(NoteAttributes.bold, value: true, range: marked) #expect(NoteDocument.markdown(contents).string == "**fox 🦊** \n") for source in ["- [ ] todo \n", "first \nsecond \t\n", "trailing "] { let imported = NoteDocument.importMarkdown(NSAttributedString(string: source)) let expected = source.hasPrefix("- [ ] ") ? String(source.dropFirst(6)) : source #expect(imported.string == expected) } }